七牛云这个API,让我轻松搞定Banner背景自动切换的功能

一、背景概述

我是谁?我是一名前端攻城狮,这周刚到公司不久,我司的产品经理就跑到我面前说:“浪哥,昨晚我看到某 APP 首页 Banner 切换时,Banner 区域的背景色会跟随 Banner 图片的色调一起切换,这个功能我们能实现么?”。听完他的话,我在脑海中脑补了一下他描述的功能,然后淡淡的回了一句 —— 应该可以吧。话音刚落,他立马来了一句 —— ”那就这么定了,我们在下个版本就上线这个功能“。

ef3da14ca29de3c60209f50793f7a478.png

竟然还有这么玩的,看来下次不能随随便便使用 应该” 二字了。既然是自己挖的坑,那还得自己填,所以我重新梳理了一下功能需求。之后,发现其实这个功能最核心的难点就是提取图片的主题色。那么接下来,我们的重心就是寻找如何提取图片主题色的方案。

二、提取主题色方案探索

这当然难不倒一个熟练掌握百度、谷歌、Bing 等主流搜索引擎的程序猿,经过一番信息检索与筛选。在 如何对前端图片主题色进行提取?这篇文章详细告诉你 这篇文章中,我找到了比较常用的主题色提取算法,主要包括 最小差值法、中位切分法、八叉树算法、聚类、色彩建模法 等(可以考虑放几张算法概述图?)。第一眼看到这么多陌生的算法后,我的第一感受是这样的:

b31b8f761657969e46baa023f72cdcb0.png

我只是想提取图片的主题色啊,能不能来点简单粗暴的方法。经过一番搜索,我找到了一种纯 CSS 实现的方案。在 小技巧!CSS 提取图片主题色功能探索 这篇文章中,介绍了通过 filter: blur()transform: scale() 来获取图片的主题色。具体的实现效果如下图所示:

94a30617af68fd12ddf64cf1dddedd95.png

在线示例:https://codepen.io/Chokcoco/pen/poRBQGg

这种方案实现起来并不会复杂,但会存在以下的问题:

  • 只能大致拿到图片的主题色,结果并不是很精确;

  • 模糊滤镜比较消耗性能,如果在页面上大量使用的话,可能会对应用的性能造成影响。

很明显 CSS 方案虽然实现起来比较简单,但并不是最好的方案。既然 CSS 方案行不通,那么我们就把目光转向 JS 方案。功夫不负有心人,在全球最大的程序员交流平台上,我找到了 color-thief 这个项目。该项目通过 JS 实现了从图片中获取调色板的功能,同时支持浏览器和 Node.js 环境。

5c9bcb65e377090414e546491550ff1d.png

(图片来源:https://lokeshdhakar.com/projects/color-thief/)

上图是 color-thief 项目提供的在线示例,提取图片调色板的效果,图中的 Dominat Color 表示图片的主题色。经过一番测试,发现该库提取图片调色板的功能还是挺不错的,那时心里的第一感觉就是就用它了。之后,我就开始在脑海中回顾产品经理提的需求。

我司 App 首页的 Banner 区,可以配置多张不同的 Banner 图,这些图会以轮播的形式展示。因为图片使用的是线上的地址,在轮播到下一张图片的时候,Banner 区的背景就要立即发生变化。如果在浏览器端进行解析的话,就没有办法及时切换背景色了,特别对于较大的图片来说,可能会导致背景色切换延迟。因为这些问题,我们只能考虑在服务端进行图片主题色提取了。心想幸亏 color-thief 也支持 Node.js 环境,不过没过几秒,就发现情况不对了。我们 App 首页上的 Banner 图片,在管理后台上传的时候,是直接传到七牛云 CDN 的,不是上传到我司的服务器。

45ee66e7c8c078f1912a85ed257533c8.png

难道为了开发这个功能,我们要调整 Banner 图片的上传方式?直觉告诉我,这种方案肯定不太合适。那么应该如何处理呢?要不翻翻七牛云的官方文档,看能不能找到一些有用的信息。不看不知道,一看吓一跳。七牛云智能多媒体服务 竟然为了开发者提供了十几种的图片处理功能:

  • 图片瘦身

  • 图片压缩

  • 图片水印

  • 图片盲水印

  • 图片 EXIF 信息

  • 图片圆角处理

  • 图片平均色调

  • 动图合成

  • 图片全景拼接

  • 图片样式

  • ...

那时我第一眼就看中了 图片平均色调 这个功能,该功能的介绍如下图所示:

891ae2bb028943067aad1f59141a8cd7.png

(图片来源:https://developer.qiniu.com/dora/1268/image-average-hue-imageave)

使用起来也很简单,直接在七牛云图片资源后面添加 imageAve 查询参数即可,成功请求之后就会以 JSON 的形式返回图片的 RGB 信息:

{
  "RGB": "0x85694d"
}

三、功能实现

之后,我测试了多张图片并把测试结果反馈给产品经理。在产品经理确认之后,我就开始着手开发上述功能。不过在具体开发前,我对 Banner 图片的处理过程进行了梳理,具体的流程如下图所示:

bbaaef973d8abbbc3807c91434b73f19.png

在经过上述的流程处理后,我们就可以取得每张 banner 图片对应的 url 地址和 rgb 图片平均色调的值。有了这些信息之后,我们就只要在 banner 图片切换的时候,同步更新该 banner 图片对应背景色即可。为了提高用户的视觉体验,我们对背景色进行了渐变处理。

在看具体的核心代码之前,我们先来看一下实际的运行效果:

91f9944b5a4110fa88746909d91ba88b.gif

看完上述的实际效果之后,接下来我们来简单介绍一下相关代码。

图片数据

// 实际项目中,该数据是从服务端接口中获取
const images = [
  {
    url: "https://***.jpg", // 七牛云图片地址
    rgb: "0xa28c60", // 当前图片对应的平均色调
  },
  ...
  {
    url: "https://***.jpg",
    rgb: "0x98aea5",
  },
];

Vue 页面模板

<template>
  <div class="block">
    <div class="banner-bg" :style="{ 'background-image': bgColor }"></div>
    <el-carousel
      @change="changeCard"
      trigger="click"
      height="150px"
      :initial-index="initialIndex"
      v-if="bannerImages.length > 0"
    >
      <el-carousel-item v-for="(image, index) in bannerImages" :key="index">
        <img class="slide-img" :src="image.url" />
      </el-carousel-item>
    </el-carousel>
  </div>
</template>

Vue 逻辑代码

export default defineComponent({
  name: "App",
  data() {
    return {
      bannerImages: [], // Banner上要显示的图片列表
      initialIndex: 0, // 初始索引值
      bgColor: "none", // 默认背景颜色
    };
  },
   mounted() {
    this.bannerImages = images.map((image, index) => {
      const bannerImage: Record<string, any> = { ...image };
      const bannerBgColor = image.rgb.slice(2, 8); // 获取banner背景色
      const nextBannerBgColor =
        index === images.length - 1
          ? images[0].rgb.slice(2, 8)
          : images[index + 1].rgb.slice(2, 8);
      bannerImage.startRgb = hex2Rgb(`#${bannerBgColor}`);
      bannerImage.endRgba = hex2Rgb(`#${bannerBgColor}`, 0.8);
      bannerImage.gradientColor = gradientColors( // 生成渐变颜色
        `#${bannerBgColor}`,
        `#${nextBannerBgColor}`,
        5
      );
      const rgbArr = hex2Rgb(`#${bannerBgColor}`, null, true);
      bannerImage.lightNess =
        (rgbArr[0] * 0.2126 + rgbArr[1] * 0.7152 + rgbArr[2] * 0.0722) / 255;
      if (this.initialIndex === index) {
        this.bgColor = `linear-gradient(-180deg, ${bannerImage.startRgb} 20%, ${bannerImage.endRgba} 98%)`;
      }
      return bannerImage;
    });
  },
  methods: {
    changeCard(index) {
      this.bgColor = `linear-gradient(-180deg, ${this.bannerImages[index].startRgb} 20%, 
        ${this.bannerImages[index].endRgba} 98%)`;
    },
  },
});

在以上代码中,我们使用了 hex2RgbgradientColors 两个工具函数,它们分别用来把十六进制的颜色转成 RGB/RGBA 的格式和生成渐变颜色。它们的具体实现如下所示:

hex2Rgb 函数

export function hex2Rgb(color, opacity?: number, getRgbList?: boolean) {
  const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
  let sColor = color.toLowerCase();
  if (sColor && reg.test(sColor)) {
    if (sColor.length === 4) {
      let sColorNew = "#";
      for (let i = 1; i < 4; i += 1) {
        sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
      }
      sColor = sColorNew;
    }
    //处理六位的颜色值
    const sColorChange = [];
    for (let i = 1; i < 7; i += 2) {
      sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
    }
    if (getRgbList) {
      return sColorChange;
    }
    return typeof opacity !== "undefined"
      ? `RGBA(${sColorChange.join(",")},${opacity})`
      : `RGB(${sColorChange.join(",")})`;
  } else {
    return sColor;
  }
}

gradientColors 函数

// 计算两个颜色之间渐变值
export function gradientColors(
  startColor: string,
  endColor: string,
  step: number
) {
  const startRGB = colorRgb(startColor); //转换为rgb数组模式
  const startR = startRGB[0];
  const startG = startRGB[1];
  const startB = startRGB[2];
  const endRGB = colorRgb(endColor);
  const endR = endRGB[0];
  const endG = endRGB[1];
  const endB = endRGB[2];
  const sR = (endR - startR) / step; //总差值
  const sG = (endG - startG) / step;
  const sB = (endB - startB) / step;
  const colorArr = [];
  for (let i = 0; i < step; i++) {
    //计算每一步的hex值
    const hex = colorHex(
      "rgb(" +
        parseInt(sR * i + startR) +
        "," +
        parseInt(sG * i + startG) +
        "," +
        parseInt(sB * i + startB) +
        ")"
    );
    colorArr.push(hex);
  }
  return colorArr;
}

利用这个图片处理功能,最终完成了我司产品经理提出的需求。通过这次功能的开发,我对图片主题色提取算法也有一定的了解。另外,除了图片处理,七牛云智能多媒体服务还提供了 盲水印处理动图合成 和 图片全景拼接 等挺好玩的功能,感兴趣的小伙伴可以体验一下,点击阅读原文即可跳转到官网页面~

四、参考资源

  • 小技巧!CSS 提取图片主题色功能探索

  • 如何对前端图片主题色进行提取?这篇文章详细告诉你

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪里行舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值