获取图片主题色
我正在参加「掘金·启航计划」
作者的话
最近五一在家,不想搞科研,然后闲着也是闲着,就想着做一些有趣的效果打发时间,然后看到网易云网页版首页的海报效果,就想着自己也尝试做一个类似的效果,既然有目标了,那就开始动手实践啦 🧐,以下是效果图展示:
可以看出,海报后面的背景颜色是当前展示的主题色,感觉这么设计用户体验更好。
效果展示
先来看看作者实现的效果吧:
代码片段
大家也可以在掘金的代码片段中自己尝试一下哦 🥳
应用场景
获取图片的主题色可以用于识别和分析图片,对于一些特定的应用场景非常有用。以下是一些应用场景:
-
界面设计:获取图片的主题色可以用于界面设计中,以确保设计的一致性和协调性。
-
图片管理:获取图片的主题色可以用于图片管理,以便更好地组织和分类图片。
-
搜索引擎优化:获取图片的主题色可以用于搜索引擎优化中,以提高图片的搜索可见性。
-
色彩分析:获取图片的主题色可以用于色彩分析,以了解某些颜色的使用频率和趋势。
-
图像识别:获取图片的主题色可以用于图像识别,以便更好地识别和分类图片。
总之,获取图片的主题色可以为我们提供更多的信息,帮助我们更好地管理和分析图片,从而更好地满足不同的需求。
🥳 以上文字来源于 ChatGPT
然后举个例子🌰吧,作者找的是百度图片功能,在展示图片的时候,是会先展示图片的主题色作为占位图,然后再加载图片,同时也可以进行图片主题色的选择,选择不同主题色的图片,会筛选出对应颜色的图片,如下是相应的效果展示:
实现思路
页面结构
首先先搭建一个基本的页面结构,包括了文件选择按钮、原图展示框和主题色展示框,这个页面结构还是很容易写出来的,代码如下:
<!-- 文件选择按钮 -->
<input type="file" id="fileInput">
<br /><br />
<div id="result">
<!-- 原图展示框 -->
<div>
<h3>原图</h3>
<canvas id="imageContainer"></canvas>
</div>
<!-- 主题色展示框 -->
<div>
<h3>主色调</h3>
<div id="mainColorTone"></div>
</div>
</div>
这里作者觉得如果没有选择图片,那么展示框就应该不显示,所以加了亿点点的样式,代码如下:
#result {
display: none;
}
初始化
首先定义一些全局变量
const fileInput = document.querySelector('#fileInput'); // 文件选择按钮
const result = document.querySelector('#result'); // 展示框
const imageContainer = document.querySelector('#imageContainer'); // 原图展示框
const mainColorTone = document.querySelector('#mainColorTone'); // 主题色展示框
const whiteList = ['png', 'jpg', 'jpeg']; // 文件限定类型
然后给文件选择按钮绑定事件
fileInput.addEventListener('change', handleFileSelect, false);
文件选择函数
1. 点击按钮选择图片
那么我们一步一步来吧,首先先写一个框架:
function handleFileSelect(event) {
// ......
}
2. 获得选择的图片
代码框架完了,是不是很简单 🤪,然后实现获得选择的图片:
const file = event.target.files[0];
3. 判断图片的后缀名是否为限定的类型
因为不设置multiple属性默认是只能选一个文件,然后获取第一个就行了,让我们打印看看都有些什么东西:
可以看到有修改时间、文件名字、文件大小、文件类型,我们需要的就是文件类型,这样就可以实现判断图片的后缀名是否为限定的类型了:
const type = file.type.split('/').pop();
if (!whiteList.includes(type)) {
alert('只能选择png、jpg、jpeg类型的图片!');
return;
}
4. 文件转换成Base64格式
最后就是将读取到的文件内容转换成Base64格式的字符串(也就是src)了,这里需要使用到HTML5中提供的一个API——FileReader,这里简单的介绍一下FileReader的功能吧:
- 读取本地文件内容,包括文本文件、图片、音频、视频等。
- 将读取到的文件内容转换成Base64格式的字符串,以便于在页面中显示或传输。
- 监听文件读取的进度和状态,以便及时处理读取结果。
- 详细信息可以查看MDN的官方文档
其实读取文件的代码都是差不多的:
const reader = new FileReader();
reader.onload = event => main(event.target.result);
reader.readAsDataURL(file);
这里我们在reader读取完成后,会触发onload事件,然后在这个事件里完成获取主题色的功能,首先让我们打印一下event有什么东西:
可以发现event.target.result就是我们需要的东西
主题色处理函数
把选择的图片展示到页面上
- 创建一个图片对象
function main(src) {
const image = new Image();
image.src = src;
image.onload = function () {
// ......
}
}
- 在图片加载完成的时候,设置画布的宽高,并将图片画上去
const width = image.width * 0.5;
const height = image.height * 0.5;
imageContainer.width = width;
imageContainer.height = height;
const ctx = imageContainer.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
计算得到渐变属性的字符串属性值
- 获得图片的像素信息,并计算获得Top k的颜色值
const top = 3;
const res = getMainColor(ctx.getImageData(0, 0, width, height), top);
- 将展示框显示,并将Top k的颜色值拼接成渐变属性的字符串属性值
if (res.length) {
result.style.display = 'block';
mainColorTone.style.backgroundImage = calcGradient(res, angle);
}
calcGradient(res, angle)得到的结果大致是这样的:
linear-gradient(180deg,rgba(66,11,95,255) 33.333333333333336%,rgba(45,10,68,255) 66.66666666666667%,rgba(113,93,206,255) 100%)
统计得到前Top k的颜色值
- 首先得到像素值信息
function getMainColor(imageData, k) {
const data = imageData.data;
const len = data.length / 4;
// ......
}
- 定义一个Map保存所有像素值的个数
const map = new Map();
- 遍历每个像素点,统计个数
for (let i = 0; i < len; i++) {
const r = data[i * 4];
const g = data[i * 4 + 1];
const b = data[i * 4 + 2];
const a = data[i * 4 + 3];
const str = `${r},${g},${b},${a}`;
map.set(str, (map.get(str) ?? 0) + 1);
}
- 将像素点个数排序,并返回前Top k的颜色值
return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, k);
拼接得到渐变属性字符串
这里就直接上代码了,大家感兴趣的可以看看:
function calcGradient(color, angle = 90) {
const each = splitRange(0, 100, color.length);
let str = `linear-gradient(${angle}deg,`;
for (let i = 0; i < each.length - 1; i++) {
const rgba = color[i][0];
str += `rgba(${rgba}) ${each[i + 1]}%,`;
}
return `${str.slice(0, -1)})`;
}
从n到m等分的函数
function splitRange(n, m, numSplits) {
const step = (m - n) / numSplits;
const result = [];
for (let i = 0; i < numSplits; i++) {
result.push(n + i * step);
}
result.push(m);
return result;
}
写在最后
大家有什么问题或者建议可以评论区讨论一下,如果喜欢的话也可以点赞➕收藏 🌟,感谢大家的支持。
这个专栏是记录一些自己写的有趣的前端小Demo,大家如果喜欢可以订阅,会不定时一直更新的 🥳