Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

小程序图标变色 #351

Open
toFrankie opened this issue Nov 19, 2024 · 0 comments
Open

小程序图标变色 #351

toFrankie opened this issue Nov 19, 2024 · 0 comments
Labels
2024 2024 年撰写 CSS 与 CSS、CSS preprocessor 相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章 小程序 与微信、支付宝、百度等小程序相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented Nov 19, 2024

配图源自 Freepik

图标变色是一个常见需求,比如根据用户心情自动切换皮肤。🐶

本文以微信小程序为例。

使用 svg 标签

第一反应是用 svg + currentColor。比如:

<div class="box">
  <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 15.7715 20.4004" width="15.7715" height="20.4004" >
    <path
      d="M11.3965 4.66797C9.94141 4.66797 8.75 5.55664 7.98828 5.55664C7.17773 5.55664 6.12305 4.66797 4.85352 4.66797C2.44141 4.66797 0 6.71875 0 10.4688C0 12.8125 0.898438 15.2832 2.02148 16.875C2.97852 18.2227 3.81836 19.3066 5.0293 19.3066C6.2207 19.3066 6.74805 18.5352 8.23242 18.5352C9.73633 18.5352 10.0781 19.3066 11.3965 19.3066C12.7051 19.3066 13.5742 18.1055 14.4043 16.9238C15.3223 15.5664 15.7129 14.248 15.7227 14.1797C15.6445 14.1602 13.1445 13.1348 13.1445 10.2734C13.1445 7.79297 15.1074 6.67969 15.2246 6.5918C13.9258 4.72656 11.9434 4.66797 11.3965 4.66797ZM10.7129 3.08594C11.3086 2.36328 11.7285 1.37695 11.7285 0.380859C11.7285 0.244141 11.7188 0.107422 11.6992 0C10.7227 0.0390625 9.55078 0.644531 8.85742 1.46484C8.30078 2.08984 7.79297 3.08594 7.79297 4.08203C7.79297 4.23828 7.82227 4.38477 7.83203 4.43359C7.89062 4.44336 7.98828 4.46289 8.0957 4.46289C8.96484 4.46289 10.0586 3.87695 10.7129 3.08594Z"
    />
  </svg>
</div>
.box {
  color: #0066cc;
}

.box svg {
  fill: currentColor;
}

CodePen

动态修改父元素的颜色就能变色。

然,微信小程序不支持 <svg /> 标签。

使用 image 标签 + base64

小程序 image 标签是支持 svg 格式图片的(一些限制)。

注意,将 fill="currentColor" 的 svg 图片放到 image 标签,该图片是无法读取其本身或父级颜色的,因此呈现会默认的黑色。

要做一些转换处理:

  1. 下载 svg 图片
  2. 读取 svg 图片文件源码
  3. 替换 svg fill 属性为指定颜色
  4. 将替换后的 svg 文件转化为 base64
function genColorfulSvg(url, color) {
  const localFilePath = await downloadSvgFile(url)
  const svgSourceStr = await readSvgFile(localFilePath)
  const colorfulSvgSourceStr = replaceSvgFillColor(svgSourceStr, color)
  const colorfulSvgBase64 = genSvgBase64(colorfulSvgSourceStr)
  return colorfulSvgBase64
}
// 下载图片
async function downloadSvgFile(url) {
  return new Promise((resolve, reject) => {
    wx.downloadFile({
      url,
      success: res => {
        if (res.statusCode === 200) return resolve(res.tempFilePath)
        reject(new Error('download file fail'))
      },
      fail: err => reject(err),
    })
  })
}
// 读取 svg 文件
async function readSvgFile(localFilePath) {
  return new Promise((resolve, reject) => {
    const fs = wx.getFileSystemManager()
    fs.readFile({
      filePath: localFilePath,
      encoding: 'utf-8',
      position: 0,
      success: res => {
        const sourceFile = res.data
        resolve(sourceFile)
      },
      fail: err => reject(err),
    })
  })
}
// 替换 fill 属性
function replaceSvgFillColor(svgSourceStr, newFillColor) {
  // 匹配 fill="..." 中的非 none 值(按需调整)
  return svgSourceStr.replace(/fill="(?!none)[^"]*"/g, `fill="${newFillColor}"`)
}
// 生成 base64
function genSvgBase64(svgSourceStr) {
  return `data:image/svg+xml;base64,${base64Encode(svgSourceStr)}`
}

示例中 base64Encode() 来自掘金社区(原文),找其他也行。

将以上封装成一个自定义组件,比如:

<colorful-image color="xxx" src="xxx" />

可以在 Component 的 lifetimes.attached() 时机执行,为了避免重复下载转换,可以用 Map 将 base64 存起来。

(具体实现就不写了)

这个方案的缺点是要额外的读取、替换、转换,性能不太好。

使用 image 标签 + CSS Filter

这种方案使用 CSS Filter 函数 drop-shadow,它通常用于生成阴影效果。

常规阴影是位于图片下方的,借助 transform 进行一些位移操作,思路如下:

  1. 隐藏原图:
    1. 将图片水平方向位移足够大的距离
    2. 将图片父级设置为 overflow: hidden
  2. 让阴影显示在原图本来的位置
    1. 将 filter: drop-shadow 水平相反方向位移同样的距离

过程中遇到了一些坑,先以 Web 为例:

<img class="icon" src="../../images/star.svg" />
.icon {
  width: 100rpx;
  height: 100rpx;
  filter: drop-shadow(200rpx 0 0 #1f883d);
  transform: translateX(-200rpx);
}

▲ 黄色为原图,绿色为投影

接着,添加一个父级元素,并设为 overflow: hidden

但是 drop-shadow 有一个表现特性:

在 Chrome 浏览器下,如果一个元素的主体部分,无论以何种方式,只要在页面中不可见,其 drop-shadow 是不可见的;实体部分哪怕有 1px 可见,则 drop-shadow 完全可见。(源自张鑫旭大佬的博客)

亲测 Safari 18.1 也有相同表现,不显示阴影,而 Firefox 132 是符合预期的。

文章里提到一个解决方案是用透明边框,使元素总在屏幕可视区域内。

调整下代码:

+ <view class="icon-box">
    <img class="icon" src="../../images/star.svg" />
+  </view>
+ .icon-box {
+   width: 100rpx;
+   height: 100rpx;
+   overflow: hidden;
+ }

  .icon {
    width: 100rpx;
    height: 100rpx;
    filter: drop-shadow(200rpx 0 0 #1f883d);
    transform: translateX(-200rpx);
+   border-right: 1000px solid transparent;
  }

这样的话,在 Chrome 下是符合预期了(黄色隐藏,绿色显示),然而 Safari 下阴影并没有显示出来。

看起来有点类似这个《深究 Safari 下 border-radius & overflow 不生效的问题》,给 icon 添加 transform: translateZ(0)will-change: transform 等解决,但 Chrome 又不能加,可用 CSS Media Query 区分(小程序亲测有用)。

如果使用 wx.getSystemInfo().platform 来区分设备,要注意开发工具、PC 端、Android 端与 Chrome 表现一致,不能加 will-change: transform 等处理,否则不显示。

比如:

@supports (-webkit-hyphens: none) {
  /* Safari for macOS or iOS browsers */
  .icon {
    will-change: transform;
  }
}

测试结果,Chrome、Safari、Firefox 和 iOS 浏览器都符合预期了。

⚠️ 注意,本打算给 border-right 给一个足够大的边框宽度,以兼容各种尺寸的图标,但发现过大时在 Safari 下不会呈现阴影,暂未发现有啥规律。但通常图标不会很大,如小程序端可以设置一屏大小 750rpx 的边框宽度。

小程序组件:

<colorful-image image-class="xxx" color="xxx" src="xxx" />
  • image-class 用于指定图片宽高等
  • color 颜色
  • src 图片链接(注意,相对路径是相对于 colorful-image 组件所在的目录)
Component({
  externalClasses: ['image-class'],

  properties: {
    // Required
    src: {
      type: String,
      value: '',
    },

    color: {
      type: String,
      value: 'transparent',
    },
  },
})
{
  "component": true,
  "usingComponents": {}
}
<view class="image-class inner-image-box">
  <image class="inner-image" style="filter: drop-shadow(760rpx 0 0 {{ color }})" src="{{ src }}" />
</view>
.inner-image-box {
  overflow: hidden;
}

.inner-image {
  box-sizing: content-box;
  display: block;
  width: 100%;
  height: 100%;
  border-right: 100000px solid transparent;
  transform: translateX(-760rpx);
}

@supports (-webkit-hyphens: none) {
  .inner-image {
    will-change: transform;
  }
}

小程序代码片段

注意:Skyline 渲染模式暂不支持。

@toFrankie toFrankie added 2024 2024 年撰写 CSS 与 CSS、CSS preprocessor 相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章 小程序 与微信、支付宝、百度等小程序相关的文章 labels Nov 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2024 2024 年撰写 CSS 与 CSS、CSS preprocessor 相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章 小程序 与微信、支付宝、百度等小程序相关的文章
Projects
None yet
Development

No branches or pull requests

1 participant