uniapp实现背景颜色跟随图片主题色变化(多端兼容)

最近做uniapp项目时遇到一个需求,要求模仿腾讯视频app首页的背景颜色跟随banner图片的主题颜色变化,并且还要兼容H5、APP、微信小程序三端。

由于项目的技术栈为uniapp,所以以下使用uni-ui的组件库作为栗子

需求分析

腾讯视频app效果如下:

从上图看出,大致分为两步:

1.获取图片主题色

2.设置从上到下的主题色to白色的渐变:

background: linear-gradient(to bottom, 主题色, 白色)

获取主题色主要采用canvas绘图,绘制完成后获取r、g、b三个通道的颜色像素累加值,最后再分别除以画布大小,得到每个颜色通道的平均值即可。

搭建页面结构

page.vue

js 复制代码
<template>
  <view class="index">
    <!-- 由于获取主题色需要canvas绘制。绝对定位,把canvas移除屏幕外绘制 -->
    <canvas canvas-id="canvas" style="position: absolute;left: -400px;"></canvas>
    <!-- box:填充主题颜色容器 -->
    <view class="box" :style="[getStyle]">
      <!-- 其他内容 -->
      <view class="tabs"></view>
      <!-- 轮播图 -->
      <swiper class="swiper" :current="current" circular autoplay indicator-dots @change="onChange" :interval="3000">
        <swiper-item class="swiper-item" v-for="(url, i) in list" :key="i">
          <image :src="url" mode="aspectFill"></image>
        </swiper-item>
      </swiper>
    </view>
  </view>
</template>

<script>
  import {
    getImageThemeColor
  } from '@/utils/index'
  export default {
    data() {
      return {
        // 图片列表
        list: [],
        // 当前轮播图索引
        current: 0,
        // 缓存banner图片主题色
        colors: [],
        // 记录当前提取到第几张banner图片
        count: 0
      }
    },
    computed: {
      // 动态设置banner主题颜色背景
      getStyle() {
        const color = this.colors[this.current]
        return {
          background: color ? `linear-gradient(to bottom, rgb(${color}), #fff)` : '#fff'
        }
      }
    },
    methods: {
      // banner改变
      onChange(e) {
        this.current = e.target.current
      },
      getList() {
        this.list = [
          'https://img.zcool.cn/community/0121e65c3d83bda8012090dbb6566c.jpg@3000w_1l_0o_100sh.jpg',
          'https://img.zcool.cn/community/010ff956cc53d86ac7252ce64c31ff.jpg@900w_1l_2o_100sh.jpg',
          'https://img.zcool.cn/community/017fc25ee25221a801215aa050fab5.jpg@1280w_1l_2o_100sh.jpg',
        ]
      },
      // 获取主题颜色
      getThemColor() {
        getImageThemeColor(this, this.list[this.count], 'canvas', (color) => {
          const colors = [...this.colors]
          colors[this.count] = color
          this.colors = colors
          this.count++
          if (this.count < this.list.length) {
            this.getThemColor()
          }
        })
      }
    },
    onLoad() {
      this.getList()
      // banner图片请求完成后,获取主题色
      this.getThemColor()
    }
  }
</script>

<style>
  .box {
    display: flex;
    flex-direction: column;
    background-color: deeppink;
    padding: 10px;
  }

  .tabs {
    height: 100px;
    color: #fff;
  }

  .swiper {
    width: 95%;
    height: 200px;
    margin: auto;
    border-radius: 10px;
    overflow: hidden;
  }

  image {
    width: 100%;
    height: 100%;
  }
</style>

封装获取图片主题颜色函数

先简单讲下思路 (想直接看源码可直接跳到下面) 。先通过request请求图片地址,获取图片的二进制数据,再将图片资源其转换成base64,调用drawImage进行绘图,最后调用draw方法绘制到画布上。

CanvasContext.draw介绍

更多api使用方法可参考:uniapp官方文档

getImageThemeColor.js

js 复制代码
/**
 * 获取图片主题颜色
 * @param path 图片的路径
 * @param canvasId 画布id
 * @param success 获取图片颜色成功回调,主题色的RGB颜色值
 * @param fail 获取图片颜色失败回调
 */
export const getImageThemeColor = (that, path, canvasId, success = () => {}, fail = () => {}) => {
  // 获取图片后缀名
  const suffix = path.split('.').slice(-1)[0]
  // uni.getImageInfo({
  //   src: path,
  //   success: (e) => {
  //     console.log(e.path) // 在安卓app端,不管src路径怎样变化,path路径始终为第一次调用的图片路径
  //   }
  // })
  // 由于getImageInfo存在问题,所以改用base64
  uni.request({
    url: path,
    responseType: 'arraybuffer',
    success: (res) => {
      let base64 = uni.arrayBufferToBase64(res.data);
      const img = {
        path: `data:image/${suffix};base64,${base64}`
      }
      // 创建canvas对象
      const ctx = uni.createCanvasContext(canvasId, that);

      // 图片绘制尺寸
      const imgWidth = 300;
      const imgHeight = 150;

      ctx.drawImage(img.path, 0, 0, imgWidth, imgHeight);

      ctx.save();
      ctx.draw(true, () => {
        uni.canvasGetImageData({
          canvasId: canvasId,
          x: 0,
          y: 0,
          width: imgWidth,
          height: imgHeight,
          fail: fail,
          success(res) {
            let data = res.data;
            let r = 1,
              g = 1,
              b = 1;
            // 获取所有像素的累加值
            for (let row = 0; row < imgHeight; row++) {
              for (let col = 0; col < imgWidth; col++) {
                if (row == 0) {
                  r += data[imgWidth * row + col];
                  g += data[imgWidth * row + col + 1];
                  b += data[imgWidth * row + col + 2];
                } else {
                  r += data[(imgWidth * row + col) * 4];
                  g += data[(imgWidth * row + col) * 4 + 1];
                  b += data[(imgWidth * row + col) * 4 + 2];
                }
              }
            }
            // 求rgb平均值
            r /= imgWidth * imgHeight;
            g /= imgWidth * imgHeight;
            b /= imgWidth * imgHeight;
            // 四舍五入
            r = Math.round(r);
            g = Math.round(g);
            b = Math.round(b);
            success([r, g, b].join(','));
          },
        }, that);
      });
    }
  });
}

主题色计算公式

计算图片主题色的公式主要有两种常见的方法:平均法和主成分分析法。

平均法:

平均法是最简单的一种方法,它通过对图片中所有像素点的颜色进行平均来计算主题色。具体步骤如下:

  • 遍历图片的每个像素点,获取其RGB颜色值。
  • 将所有像素点的R、G、B分量分别求和,并除以像素点的总数,得到平均的R、G、B值。
  • 最终的主题色即为平均的R、G、B值。

主成分分析法

主成分分析法是一种更复杂但更准确的方法,它通过对图片中的颜色数据进行降维处理,提取出最能代表整个图片颜色分布的主要特征。具体步骤如下:

  • 将图片的所有像素点的颜色值转换为Lab颜色空间(Lab颜色空间是一种与人眼感知相关的颜色空间)。
  • 对转换后的颜色数据进行主成分分析,找出相应的主成分。
  • 根据主成分的权重,计算得到最能代表整个图片颜色分布的主题色。

需要注意的是,计算图片主题色的方法可以根据具体需求和算法的实现方式有所不同,上述方法只是其中的两种常见做法。

结语

大家有更好的实现方式,欢迎评论区留言哦!

往期文章回顾

table表格自适应浏览器窗口变化解决方案

一文学会vue3如何自定义hook钩子函数和封装组件

一文学会请求中断、请求重发、请求排队、请求并发

相关推荐
Patrick_Wilson25 分钟前
修好 bug 只是开始:一次由监控需求反向重构日志结构的复盘
前端·监控·数据可视化
kyriewen41 分钟前
面试官让我优化一个卡死的表格,我打开 Claude 五秒重写,他放下咖啡问我要不要来当组长
前端·javascript·面试
小粉粉hhh1 小时前
React(二)——dom、组件间通信、useEffect
前端·javascript·react.js
Csvn1 小时前
🤖 AI 生成前端代码的 5 个典型翻车现场及修复指南
前端
IT_陈寒2 小时前
SpringBoot自动装配坑了我一天,原来问题出在这
前端·人工智能·后端
索西引擎3 小时前
【React】key 属性:协调算法中的元素标识机制与最佳实践
前端·javascript·react.js
只会cv的前端攻城狮4 小时前
动态组件架构设计:从配置到事件驱动全链路解析
前端·vue.js
飞天狗4 小时前
Next.js 16 App Router 实战:服务端组件与流式渲染
前端·next.js
万敏4 小时前
Vue3 全栈实战第二周:Pinia + Vite + Router + axios 完整记录
vue.js·node.js·全栈
OpenTiny社区4 小时前
TinyEngine 2.11版本发布:AI 进一步融入画布,Vue 工程可一键转DSL
前端·vue.js·github