In the morning, the product proposed a “simple” banner development demand, originally thought it was very simple, accidentally consumed my day time, a time full of emotion, to dig gold record. Writing this article, in addition to the technical experience, is a reminder to you on your mobile phone: apps are never perfect, but be in awe of the perfectionist programmer (yourself).

What needs?

Product: Make a banner in the user center of our project to display the pictures uploaded by users, and display the pictures completely.

Me: Maybe the size of some pictures is not consistent with the box of the banner. Even if the picture is scaled, there will be left edges up and down or left and right. Is that acceptable?

Product: Yes, just leave the black edge

Me: Black edge?

Product: Black version with black edge, white version with white edge

I: oh

Then I started thinking about what to do!

What am I going to do?

Given the simplicity of the above dialogue, it is necessary to list the status of the banner:

  • The newly uploaded banner image is cut at a fixed scale
  • The picture uploaded before is complete, and the proportion is not fixed.

My first idea is to give priority to the new banner and leave the edge of the old banner:

So let’s say the ratio of our banner right now is zero2:1, using three pictures with different ratios (the first was exactly 2:1, the second was smaller, and the third was larger) :

As expected, it also meets product requirements, but I don’t want to stop there, can you reconcile the issue of margin? I was deep in thought…

Deal with left side

The first thing that comes to mind is to add a background color. Try # CCC:

The feeling is better, but the color value is fixed and not in harmony with the picture, can we take a main color from the picture as the background color? This way it won’t be so obtrusive. I was reminded of my previous article about converting color images to black and white — a pure front-end implementation.

I decided to take two main colors, one for the left and one for the right, and make a gradient for the base color:

It looks ok, at least better than before, and the technical details and code implementation will follow.

The technical details

How do you set an aspect ratio for a container in the first place?

  • throughpadding-topSet the corresponding percentage value
  • With new propertiesaspect-ratio(Safari not supported)

Here, I’m going to use the first one for compatibility. Set up a box with a 2:1 aspect ratio:

div{
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    /* 2:1, the panding percentage value is */ relative to the width of the box
    padding-top: 50%;
}
Copy the code

Again, how to get the main color of the picture? We use Canvas’s ctx.getImageData method.

There are several steps:

  • Draw the image to a Canvas element
  • Get all RGBA pixels of the image
  • Take the mean of the color of a region and find the RGBA color that is closest to the mean of the region as the main color of the region
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];
    
    // Draw to canvas
    ctx.drawImage(imgEle, 0.0);
    // Get imageData: RGBA pixels
    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) {
                // Get the RGB mean value
                const curAverageRGB = (imgData.data[i] + imgData.data[i + 1] + imgData.data[i + 2) /3;
                let leftOrRightRef = inLeft ? leftSectionData : rightSectionData;
                // Each array contains four values: the average values of r, g and b, and the three values of r, g and b.
                // On the one hand, the mean value is used to calculate the total mean value of the region, and then compare with each mean to get the index of the item closest to the total mean value, and then take the last three values in the array: RGB, corresponding to the color
                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)
}
Copy the code

Take the color and implement the element gradient:

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%, `
Copy the code

Here are a few more images to see the effect:

The last

In addition to the background image + fixed scale, there are other solutions, such as dynamically capturing the size of the image and using other parameters (such as the maximum width and height allowed by the banner) to calculate an appropriate container size. Configurability is higher, but development costs are also higher.

Finally, one more fun thing — the Web color picker:

The repository address is here: github address