使用canvas给banner图片加个颜色条

突发奇想,给滚动的banner,每个图片的顶部都加上一个和当前banner图片搭配的具备背景色的色条,这样作能够在不改变图片规格的状况下在移动端作到沉浸式。javascript

技术栈

swiper 4.0 / react / canvascss

大体思路是使用canvas在背后依次加载每一张图片,而后获取每一张图片左上角的第一个像素值,以此做为色条的背景色。html

技术重点和难点

  • 在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色条

banner1.png(图片来自互联网,侵删)
banner2.png(图片来自互联网,侵删)
banner3.png(图片来自互联网,侵删)
感谢阅读,转载请注明出处。
下期会讲解 前端的缓存实现方式,喜欢阅读的朋友能够关注个人公众号:雨茗良记,每周会按期更新文章哦,包括但不限于技术。
我是雨茗良记,一个爱作饭的程序猿😜
雨茗良记前端

相关文章
相关标签/搜索