css和图片主题色“提取”

这个想法是来源于「性能优化」中的骨架屏: 在图片居多的站点中,这将是非常nice的体验 ------ 图片加载通常是比较让人难受的,好的骨架中一般占位图就是低像素的图片,即大体配色和变化是和实际内容一致的。 有时候比如图片不固定的,那可以使用算法获取图片的主体颜色(至少得是同色系的吧),使用纯色块占位。

再进一步想到,在一些"轻松"的场景下,我们可以让背景色/页面主题色跟随轮播图改变。至于效果嘛......你们可以想一下网易云音乐滑动切歌时的背景效果。

因为是不固定图片,所以我想到了四种方法:

  • tensorflow.js 图像色彩分析
  • canvas对图片主基调进行分析,取大概值
  • css高斯模糊
  • 上传图片时后端对图片分析处理,返回时直接返回一张低像素图片

第一种方式目前还在我的实践中,以后会单独出一篇文章;最后一种方式个人不太建议首选:首先后端处理也需要时间,另一方面毕竟也是以图片进行传输的...yee~(而且后端可能也不太建议你首选🤣)

想看实际效果的推荐自己动手试下,因为我发现本文中用QQ截屏截取的图片怎么都这么暗啊,实际展示的还是挺漂亮的。

第三种方式看起来是纯css实现的,怎么获取呢?这就要说到css中的filter: blur(); 简单来说,利用模糊滤镜及进一步拉伸,可以近似地拿到一张图片的主题色:

html 复制代码
<div></div>
css 复制代码
div {
	background: url(图片地址);
	background-size: cover;
	filter: blur(50px);
}

你看,通过比较大的一个模糊滤镜,将图片高斯模糊50px,模糊后的图片是不是有点内味了,

不过还不行,存在一些模糊边缘,我们可以利用overflow进行剪裁。

接下来,我们需要去掉模糊的边角,以及通过transform: scale()放大效果,将颜色进一步聚焦: 这里就很推荐使用伪元素进行操作了

css 复制代码
div {
	position: relative;
	width: xx;
	height: xx;
	overflow: hidden;
}
div::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: url(图片地址);
	background-size: cover;
	filter: blur(50px);
	transform: scale(2); //自行更改
	transform-origin: center center;
}

这样就拿到图片的主色调了。当然是要进行其他处理的。

再来说说第二种方法 ------ canvas。其实也不建议,因为本身就是JS操作,而在图片又不固定又有些多的情况下单线程的js处理这种"一级事件"造成的性能和体验感的损失是不可想象的。但本文笔者还是要分享一下,因为这是我当初研究的第一个被应用的成果(有情怀了嘿嘿)

首先,canvas中的getImageData()方法可以获取图片的像素集合:

javascript 复制代码
function getImagePixel(canvas, img) {
	const context = canvas.getContext("2d");
	context.drawImage(img, 0, 0);
	return context.getImageData(0, 0, canvas.width, canvas.height).data;
}

这里对使用canvas不熟悉的同学提个醒:img是异步加载的,所有对图片的操作都要放在 img 的 onload 中进行 ------ 你可以考虑用 promise 做这件事。

调用这个函数会拿到一个数组 ------ 它是rgba值,也就是说,处理时四个数据为"一组",更通俗地说,for循环中i+=4!来处理一下数据:

javascript 复制代码
function getCountArr(pData) {
	let colorList = [], rgba = [], rgbaStr = '';
	for(let i=0; i<pData.length; i+=4) {
		rgba[0] = pData[i];
		rgba[1] = pData[i+1];
		rgba[2] = pData[i+2];
		rgba[3] = pData[i+3];
		if(rgba.indexOf(undefined)!==-1 || pData[i+3] === 0) {
			continue;
		}
		rgbaStr = rgba.join(',');
		if(rgbaStr in colorList) {
			++colorList[rgbaStr];
		}else {
			colorList[rgbaStr] = 1;
		}
	}
	return colorList;
}

这个时候,得到的就是每组数据(色值)出现的次数了。 然后改写刚刚的getImagePixel函数:

javascript 复制代码
return getCountArr(pixelData);

至此,我们将其排序并取出第一个值/或者取出某些标志项的平均值,基本上就可以将其作为 background 值了!


峰回路转!

你难道真觉得canvas的这种方法只是鸡肋?那试想这样一种场景:在弱网情况下,图片必定贼慢才能加载出来。这时候我们通过js拿到图片的主色调并填充到图片的位置中。这是不是一个"模糊渐变加载"的绝佳场景! 而且,笔者曾经遇到这样一个场景:往图片上添加文字。这时候你就需要注意一个问题,图片主色调。用canvas分析图片的主要颜色或平均色可以在深色调时添加白色文字在浅色调时添加黑色文字!

笔者前段时间弄了一个微信公众号:前端Code新谈。里面暂时有webrtc、前端面试和用户体验系列文章,欢迎关注!希望能够帮到大家,也希望能互相交流!共同进步

相关推荐
子兮曰6 小时前
OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
前端·javascript·github
百锦再7 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
莲华君7 小时前
React快速上手:从零到项目实战
前端·reactjs教程
百锦再7 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
易安说AI7 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
失忆爆表症9 小时前
05_UI 组件库集成指南:Shadcn/ui + Tailwind CSS v4
前端·css·ui
小迷糊的学习记录9 小时前
Vuex 与 pinia
前端·javascript·vue.js
发现一只大呆瓜9 小时前
前端性能优化:图片懒加载的三种手写方案
前端·javascript·面试
不爱吃糖的程序媛10 小时前
Flutter 与 OpenHarmony 通信:Flutter Channel 使用指南
前端·javascript·flutter
利刃大大10 小时前
【Vue】Element-Plus快速入门 && Form && Card && Table && Tree && Dialog && Menu
前端·javascript·vue.js·element-plus