「掘金·启航计划」仿网易云首页获取图片的主题色

981 阅读5分钟

获取图片主题色

我正在参加「掘金·启航计划」

作者的话

最近五一在家,不想搞科研,然后闲着也是闲着,就想着做一些有趣的效果打发时间,然后看到网易云网页版首页的海报效果,就想着自己也尝试做一个类似的效果,既然有目标了,那就开始动手实践啦 🧐,以下是效果图展示:

网易云音乐效果展示.png

可以看出,海报后面的背景颜色是当前展示的主题色,感觉这么设计用户体验更好。

效果展示

先来看看作者实现的效果吧:

屏幕录制2023-05-03 09.31.23.gif

代码片段

大家也可以在掘金的代码片段中自己尝试一下哦 🥳

应用场景

获取图片的主题色可以用于识别和分析图片,对于一些特定的应用场景非常有用。以下是一些应用场景:

  1. 界面设计:获取图片的主题色可以用于界面设计中,以确保设计的一致性和协调性。

  2. 图片管理:获取图片的主题色可以用于图片管理,以便更好地组织和分类图片。

  3. 搜索引擎优化:获取图片的主题色可以用于搜索引擎优化中,以提高图片的搜索可见性。

  4. 色彩分析:获取图片的主题色可以用于色彩分析,以了解某些颜色的使用频率和趋势。

  5. 图像识别:获取图片的主题色可以用于图像识别,以便更好地识别和分类图片。

总之,获取图片的主题色可以为我们提供更多的信息,帮助我们更好地管理和分析图片,从而更好地满足不同的需求。

🥳 以上文字来源于 ChatGPT

然后举个例子🌰吧,作者找的是百度图片功能,在展示图片的时候,是会先展示图片的主题色作为占位图,然后再加载图片,同时也可以进行图片主题色的选择,选择不同主题色的图片,会筛选出对应颜色的图片,如下是相应的效果展示:

百度图片效果展示.gif

实现思路

页面结构

首先先搭建一个基本的页面结构,包括了文件选择按钮、原图展示框和主题色展示框,这个页面结构还是很容易写出来的,代码如下:

<!-- 文件选择按钮 -->
<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属性默认是只能选一个文件,然后获取第一个就行了,让我们打印看看都有些什么东西:

截屏2023-05-03 09.54.50.png

可以看到有修改时间、文件名字、文件大小、文件类型,我们需要的就是文件类型,这样就可以实现判断图片的后缀名是否为限定的类型了:

const type = file.type.split('/').pop();
if (!whiteList.includes(type)) {
    alert('只能选择png、jpg、jpeg类型的图片!');
    return;
}

4. 文件转换成Base64格式

最后就是将读取到的文件内容转换成Base64格式的字符串(也就是src)了,这里需要使用到HTML5中提供的一个API——FileReader,这里简单的介绍一下FileReader的功能吧:

  1. 读取本地文件内容,包括文本文件、图片、音频、视频等。
  2. 将读取到的文件内容转换成Base64格式的字符串,以便于在页面中显示或传输。
  3. 监听文件读取的进度和状态,以便及时处理读取结果。
  4. 详细信息可以查看MDN的官方文档

其实读取文件的代码都是差不多的:

const reader = new FileReader();
reader.onload = event => main(event.target.result);
reader.readAsDataURL(file);

这里我们在reader读取完成后,会触发onload事件,然后在这个事件里完成获取主题色的功能,首先让我们打印一下event有什么东西:

截屏2023-05-03 10.08.40.png

可以发现event.target.result就是我们需要的东西

主题色处理函数

把选择的图片展示到页面上

  1. 创建一个图片对象
function main(src) {
    const image = new Image();
    image.src = src;
    image.onload = function () {
        // ......
    }
}
  1. 在图片加载完成的时候,设置画布的宽高,并将图片画上去
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);

计算得到渐变属性的字符串属性值

  1. 获得图片的像素信息,并计算获得Top k的颜色值
const top = 3;
const res = getMainColor(ctx.getImageData(0, 0, width, height), top);
  1. 将展示框显示,并将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的颜色值

  1. 首先得到像素值信息
function getMainColor(imageData, k) {
    const data = imageData.data;
    const len = data.length / 4;
    // ......
}
  1. 定义一个Map保存所有像素值的个数
const map = new Map();
  1. 遍历每个像素点,统计个数
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);
}
  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,大家如果喜欢可以订阅,会不定时一直更新的 🥳