产品:“你前端,写个 banner 这么费劲?”
共 2248字,需浏览 5分钟
·
2022-05-13 08:59
点击上方 前端Q,关注公众号
回复加群,加入前端Q技术交流群
这篇文章,除了技术经验之外,还想给手机前的你一点提醒:程序从来都不是完美的,但请对那些追求完美的程序员(自己)保持着敬畏。
什么需求?
产品:在我们项目用户中心中搞个banner,展示用户上传的图片,要完整展示图片。
我:可能有些图片尺寸跟banner的盒子不一致,即使图片缩放,也会导致上下或者左右有留边的现象,可以接受吗?
产品:可以,留黑边即可
我:黑边?
产品:黑色版用黑边,白色版用白边
我:哦
然后,我就开始想怎么做了!
我打算怎么做?
鉴于以上对白很朴素,很有必要把banner的情况列一下:
新上传的banner图比例是固定比例剪切的 之前上传的是完整图片,比例是不固定的。
我的第一个想法是优先新banner,老banner留边即可:
假如现在banner的比例是2:1
,用三张不同比例的图片(第一个是图片比例刚好是2:1,第二个比例更小,第三个比例更大)测试的结果:
跟预期的一样,也符合产品需求,不过我不想就此停步,能否调和一下留边问题呢?我陷入了深思...
处理留边
首先想到的是增加底色,来个#ccc
试试:
感觉好一些了,但颜色值是固定死的,跟图片不太协调,能否从图片里取一个主色作为底色呢?这样就不会这么突兀了。
我决定取两个主色,一个是左半区的,一个是右半区的,然后用这俩颜色搞个渐变作为底色:
看起来还凑合,至少比之前好多了,接下来介绍相关技术细节和代码实现。
技术细节
首先如何给一个容器设置宽高比呢?
通过 padding-top
设置对应的百分比值通过新属性 aspect-ratio
(safari不支持)
这里,为了兼容性,我用的第一种。设置一个长宽比为2:1
的盒子:
div{
background-size: contain;
background-repeat: no-repeat;
background-position: center;
/* 2:1 ,panding百分比值是相对于盒子的宽度的*/
padding-top: 50%;
}
再次,如何获取图片的主色呢?我们借助Canvas的ctx.getImageData
方法。
分一下几个步骤:
将图片绘制到一个canvas元素上 获取图像所有的rgba像素点 取某个区域颜色的均值,并找出这个区域最接近均值的rgba颜色,作为该区域的主色
var imgSrc = "XXXXX"
const imgEle = document.createElement('img')
const canvas = document.createElement('canvas')
imgEle.src = imgSrc
imgEle.onload = () => {
var ctx = canvas.getContext("2d");
var naturalImgSize = [imgEle.naturalWidth, imgEle.naturalHeight];
canvas.width = naturalImgSize[0];
canvas.height = naturalImgSize[1];
//绘制到canvas
ctx.drawImage(imgEle, 0, 0);
//获取imageData:rgba像素点
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const leftSectionData = []
const rightSectionData = []
const oneLineImgDataLen = canvas.width * 4;
imgData.data.forEach((colorVal, i) => {
if (i % onelineImgDataLen <= 0.5 * onelineImgDataLen || i % onelineImgDataLen >= 0.6 * onelineImgDataLen) {
const inLeft = i % onelineImgDataLen <= 0.5 * onelineImgDataLen
if (i % 4 === 0) {
// 获取rgb均值
const curAverageRGB = (imgData.data[i] + imgData.data[i + 1] + imgData.data[i + 2]) / 3;
let leftOrRightRef = inLeft ? leftSectionData : rightSectionData;
//每个数组里存四个值:本颜色值中的r、g、b的均值,以及r、g、b三个值。
//均值一方面用于累加计算本区域的整体均值,然后再跟每个均值对比拿到与整体均值最接近的项的索引,再取该数组里的后三个值:rgb,对应着颜色
leftOrRightRef[leftOrRightRef.length] = [curAverageRGB, imgData.data[i], imgData.data[i + 1], imgData.data[i + 2]]
}
}
})
//generate average rgb
const averageOfLeft = Math.round(leftSectionData.reduce((_cur, item) => {
return _cur + item[0]
}, 0) / leftSectionData.length)
const averageOfRight = Math.round(rightSectionData.reduce((_cur, item) => {
return _cur + item[0]
}, 0) / rightSectionData.length)
//find the most near color
const findNearestIndex = (averageVal, arrBox) => {
let _gapValue = Math.abs(averageVal - arrBox[0])
let _nearColorIndex = 0
arrBox.forEach((item, index) => {
const curGapValue = Math.abs(item - averageVal)
if (curGapValue < _gapValue) {
_gapValue = curGapValue
_nearColorIndex = index
}
})
return _nearColorIndex
}
const leftNearestColor = leftSectionData[findNearestIndex(averageOfLeft, leftSectionData)]
const rightNearestColor = rightSectionData[findNearestIndex(averageOfRight, rightSectionData)]
console.log(leftNearestColor,rightNearestColor)
}
取到颜色,实现元素的渐变:
element.style.backgroundImage = `url("XXXX"),linear-gradient(90deg,rgba(${leftNearestColor[1]},${leftNearestColor[2]},${leftNearestColor[3]},1) 0%,rgba(${rightNearestColor[1]},${rightNearestColor[2]},${rightNearestColor[3]},1) 100%`
这里再多选几张图片看效果:
最后
其实,除了背景图+固定比例还有其他的方案,比如通过动态获取图片的尺寸,配合其他参数(如banner允许的最大宽度和高度)可以计算出一个合适的容器尺寸。可配置性更高,但是开发成本也会高一些。
最后,再来个好玩的东西--web颜色拾取器:
仓库地址在这里:https://github.com/anderlaw/color-picker-demo
作者:AndyLaw
https://juejin.cn/post/7068590848228720671
往期推荐
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...