突發奇想,給滾動的banner,每一個圖片的頂部都加上一個和當前banner圖片搭配的具有背景色的色條,這樣做可以在不改變圖片規格的情況下在移動端做到沉浸式。
技術棧
swiper 4.0 / react / canvas
大致思路是使用canvas在背後依次載入每一張圖片,然後獲取每一張圖片左上角的第一個畫素值,以此作為色條的背景色。
技術重點和難點
- 在html模板中插入display為none的canvas元素
- 使用promise併發載入遠端圖片
- 如果圖片儲存到cdn上,需要在cdn配置允許跨域訪問的header屬性(Access-Control-Allow-Origin: 指定的域名資訊或者暴力的使用*來允許所有域名訪問)
- 圖片設定crossOrigin屬性為anonymous,使得canvas可以讀取影像資料
- 通過componentDidUpdate生命週期控制swiper的初始化
- 使用快取技術,防止每次載入banner重複計算畫素值
"talk is cheap, show me the code ?"
App.js
import React, { Component } from 'react';
import Banner from './Banner.js';
import banners from '../public/banner.json';
class App extends Component {
constructor(props) {
super(props)
this.state = {
banners: []
}
this.colorCache = JSON.parse(localStorage.getItem('bannerColor')) || {} // 有快取直接使用
}
componentDidMount() {
this.processBanners(banners)
}
processBanners(banners) {
const that = this;
const canvasDom = document.getElementById("cavans")
const context = canvasDom.getContext('2d')
const imgPromises = []
for (let i = 0, j = banners.length; i < j; i++) {
if (this.colorCache[banners[i].imgUrl]) continue // 有快取直接使用,避免重複計算
const imgObj = new Image()
imgObj.crossOrigin = "Anonymous" // 允許跨域載入給canvas使用
imgObj.src = banners[i].imgUrl
imgPromises.push(imageLoad(imgObj, banners[i]))
}
Promise.all(imgPromises).then(values => {
for (let i = 0, j = values.length; i < j; i++) {
const { img, banner } = values[i]
context.drawImage(img, 0, 0);
const colors = context.getImageData(0, 0, 1, 1); // 取出圖片左上角第一個畫素值
const colorStr = `rgba(${[colors.data[0], colors.data[1], colors.data[2], colors.data[3]].join(',')})`;
this.colorCache[banner.imgUrl] = colorStr // 把計算出的色值,以圖片url為key,儲存到快取中
banner.bgColor = colorStr // banner資料中新增bgColor屬性,表明背景色
}
localStorage.setItem('bannerColor', JSON.stringify(this.colorCache))
that.setState({ banners })
})
}
render() {
return (
<div>
<canvas id="cavans" style={{display: 'none'}}/>
<Banner res={this.state.banners} colorCache={this.colorCache} />
</div>
);
}
}
// 使用promise封裝的載入圖片的方法,便於併發load圖片
function imageLoad(imgObj, banner) {
return new Promise((resolve, reject) => {
imgObj.onload = function () {
const img = this;
resolve({ img, banner })
}
})
}
export default App;
複製程式碼
Banner.js
import React, { Component } from 'react';
import Swiper from 'swiper';
import "../node_modules/swiper/dist/css/swiper.min.css"
class Banner extends Component {
constructor(props) {
super(props);
this.bannerContainerRef = React.createRef();
}
componentDidUpdate() {
// 通過判斷document中是否存在swiperContainer DOM,來初始化swiper
if (this.bannerContainerRef) {
this.initSwiper()
}
}
initSwiper() {
new Swiper(".swiper-container", {
direction: "horizontal",
autoplay: true
})
}
render() {
// 沒有banner資料時,顯示loading資訊
if (!this.props.res.length) return <div>Loading....</div>
return <div className="swiper-container" ref={this.bannerContainerRef}>
<div className="swiper-wrapper">
{
this.props.res.map(banner => {
return (
<div className="swiper-slide" key={banner._id}>
<div style={{ backgroundColor: this.props.colorCache[banner.imgUrl] }}>header</div>
<img width="100%" alt={banner.name} src={banner.imgUrl} />
</div>
)
})
}
</div>
</div>
}
}
export default Banner;
複製程式碼
banner.json
[{
"_id": "5d5539521848690226d12555",
"imgUrl": "./picture1.png",
"name": "picture1.png"
}, {
"_id": "5d561fb6184869336dd12661",
"imgUrl": "./picture2.png",
"name": "picture2.png"
}, {
"_id": "5d552c1218486991f2d124c3",
"imgUrl": "./picture3.png",
"name": "picture3.png"
}]
複製程式碼
最後效果,不同的banner有不同的header色條
(圖片來自網際網路)感謝閱讀,轉載請註明出處。
下期會講解 前端的快取實現方式,喜歡閱讀的朋友也可以關注我的公眾號:雨茗良記,每週會定期更新文章哦,包括但不限於技術。
我是雨茗良記,一個愛做飯的程式猿?