產品:“你前端,寫個 banner 這麼費勁?”

語言: CN / TW / HK

點選上方  前端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」,認真學前端,做個專業的技術人...

點個 在看 支援我吧