产品:“写个banner都这么费劲?还要排期一天”

前端阳光

共 2408字,需浏览 5分钟

 ·

2022-03-04 22:05

前言

上午产品提了个“简单”的banner开发的需求,本来以为很简单,一不小心消耗了我一天的时间,一时间感慨万千,来掘金记录一下。写这篇文章,除了技术经验之外,还想给手机前的你一点提醒:程序从来都不是完美的,但请对那些追求完美的程序员(自己)保持着敬畏。

什么需求?

产品:在我们项目用户中心中搞个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-top50%;
}

再次,如何获取图片的主色呢?我们借助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, 00);
    //获取imageData:rgba像素点
    var imgData = ctx.getImageData(00, 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允许的最大宽度和高度)可以计算出一个合适的容器尺寸。可配置性更高,但是开发成本也会高一些。

作者:AndyLaw 链接:https://juejin.cn/post/7068590848228720671


浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报