what is mask-image

mask-image CSS属性用于设置元素上遮罩层的图像。

MDN是这样来表述mask-image的。关于它的兼容性,你可以在这里看到Can i use mask-image

2021年,是时候用mask-image来展示你炫酷的渐变图标了_mask-image

  • Chrome全系支持
  • Firefox v53起
  • Safari v4起

到目前为止,我们想要正常地使用该CSS属性,需要做一下兼容,用-webkit-mask-image:

.icon {      width: 24px;      height: 24px;      mask-repeat: no-repeat;      mask-size: cover;      background: black;      mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M368 368L144 144M368 144L144 368'/%3E%3C/svg%3E");      -webkit-mask-size: cover;      -webkit-mask-repeat: no-repeat;      -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M368 368L144 144M368 144L144 368'/%3E%3C/svg%3E");
}复制代码

why mask-image

看一下市面上的icon解决方案:

  • icon图片 使用img标签,或者作为背景图片

    • 优点:直接且方便,不需要任何额外的支持,兼容性最好
    • 缺点:多次http请求,且没有任何灵活性,无法满足多场景下的的需求
  • svg symbol 使用use标签,动态添加图标

    • 优点:比较灵活,支持多色
    • 缺点:需要把图标写入到模版,过于影响开发体验
  • iconfont 基于base64和雪碧图

    • 优点:合并为一张图,仅一次http请求;兼容性好;相对灵活
    • 缺点:编码为base64之后,图标磁盘占用变大了;base64相对于原始svg,显著影响页面渲染速度;

而使用了mask-image之后,上述问题都不复存在。mask-image十分灵活,它支持一个特别炫酷的特性,就是渐变色(本质是背景叠加),它的表现原理类似于PS中针对图层的“混合选项”=>“渐变叠加”。

我甚至以为,mask-image这个属性就是依据PS的“颜色叠加”、“渐变叠加”来实现的,因为二者的作用,十分相似。

有了mask-image,你可以制造出各种意想不到的效果:2021年,是时候用mask-image来展示你炫酷的渐变图标了_mask-image_02

下面基于mask-image分别封装一个小程序组件和React组件。

小程序组件

<!--index.wxml--><wxs  src="./index.wxs"  module="wxs"></wxs><view  class="icon_wrap"  style="{{wxs.getWrapStyles({size,bordered,filled,round,borderColor,fillColor})}}"  wx:if="{{visibleWrap}}">  <viewclass="icon"style="{{wxs.getStyles({size,color,src})}}"  ></view></view><view  class="icon"  style="{{wxs.getStyles({size,color,src})}}"  wx:else></view>复制代码
// index.wxsfunction getWrapStyles (props){	var styles = ''if (props.bordered) styles += ';border:2rpx solid ' + props.borderColor	if (props.filled) styles += ';background:' + props.fillColor	if (props.round) styles += ';border-radius:50%'

	styles += ';width:' + props.size * 1.5 + 'px'
	styles += ';height:' + props.size * 1.5 + 'px'return styles
}function getStyles (props){	var styles = ''

	styles += ';width:' + props.size + 'px'
	styles += ';height:' + props.size + 'px'
	styles += ';background:' + props.color
	styles += ';mask-image:url("' + props.src + '")'
	styles += ';-webkit-mask-image:url("' + props.src + '")'return styles
}module.exports = {	getWrapStyles: getWrapStyles,	getStyles: getStyles
}复制代码
// index.tsComponent({	options: {		//@ts-ignorepureDataPattern: /^(type)$/
	},	properties: {		icon: <{			type: ObjectConstructor			value: { outline: string; filled: string }
		}>{			type: Object,			value: {}
		},		type: <{			type: StringConstructor			value: 'outline' | 'filled'
		}>{			type: String,			value: 'outline'
		},		size: {			type: Number,			value: 20
		},		color: {			type: String,			value: '#000000'
		},		visibleWrap: {			type: Boolean,			value: false
		},		bordered: {			type: Boolean,			value: false
		},		filled: {			type: Boolean,			value: false
		},		round: {			type: Boolean,			value: false
		},		borderColor: {			type: String,			value: 'whitesmoke'
		},		fillColor: {			type: String,			value: 'whitesmoke'
		}
	},	observers: {		type (v) {			this.getSrc(this.data.icon[v])
		},
		icon (v) {			if (!v) returnif (!this.data.type) returnthis.getSrc(v[this.data.type])
		}
	},	data: {		src: '',		height: 20,		width: 20
	},	methods: {
		getSrc (svg) {			if (!svg) returnthis.setData({				src: 'data:image/svg+xml,' + svg.replace(/</g, '%3C').replace(/>/g, '%3E')
			})
		}
	}
})复制代码
// index.less.icon_wrap {      display: flex;      justify-content: center;      align-items: center;      box-sizing: border-box;
}.icon {      vertical-align: middle;      display: inline-block;      background: black;      mask-repeat: no-repeat;      mask-size: cover;
}复制代码
// icon.tsexport const cube = {	outline: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path d='M448 341.37V170.61A32 32 0 00432.11 143l-152-88.46a47.94 47.94 0 00-48.24 0L79.89 143A32 32 0 0064 170.61v170.76A32 32 0 0079.89 369l152 88.46a48 48 0 0048.24 0l152-88.46A32 32 0 00448 341.37z' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M69 153.99l187 110 187-110M256 463.99v-200'/></svg>`,	filled: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path d='M440.9 136.3a4 4 0 000-6.91L288.16 40.65a64.14 64.14 0 00-64.33 0L71.12 129.39a4 4 0 000 6.91L254 243.88a4 4 0 004.06 0zM54 163.51a4 4 0 00-6 3.49v173.89a48 48 0 0023.84 41.39L234 479.51a4 4 0 006-3.46V274.3a4 4 0 00-2-3.46zM272 275v201a4 4 0 006 3.46l162.15-97.23A48 48 0 00464 340.89V167a4 4 0 00-6-3.45l-184 108a4 4 0 00-2 3.45z'/></svg>`}复制代码

在应用中使用:

  • 导入图标
import { cube } from 'icon'Page({data:{icon:{
            cube
        }
    }
})复制代码
  • 使用组件
<Iconicon="{{icon.cube}}"color="black"size="{{20}}"type="filled"></Icon>复制代码

通过直接传入svg使用,这样对图标进行按需加载,利于缩减包大小和后续扩展项目。上述这种方式有利有弊:牺牲了运行时内存,但换取的是包大小和渲染效率。

上面的小程序组件经过很少的改动(wxs => computed)就可以用作Vue组件。

React组件

// index.tsximport { useState, useEffect } from 'react'import { getWrapStyles, getStyles, getSrc } from './utils'import styles from './index.less'interface IProps {	icon: { outline: string; filled?: string }
	type?: 'outline' | 'filled'
	size?: number
	color?: string
	visibleWrap?: boolean
	bordered?: boolean
	filled?: boolean
	round?: boolean
	borderColor?: string
	fillColor?: string
}const Index = (props: IProps) => {	const {
		icon,
		type = 'outline',
		size = 20,
		color = 'black',
		visibleWrap,
		bordered,
		filled,
		round,
		borderColor = 'whitesmoke',
		fillColor = 'whitesmoke'
	} = props	const [ state_src, setStateSrc ] = useState('')

	useEffect(		() => {			if (!icon) returnif (!type) return

			setStateSrc(getSrc(icon[type]))
		},
		[ icon, type ]
	)	return (		<div className={styles._local}>
			{visibleWrap ? (				<divclassName='icon_wrap'style={getWrapStyles({size,						bordered,						filled,						round,						borderColor,						fillColor
					})}
				><divclassName='icon'style={getStyles({ size, color, src: state_src })}
					/></div>
			) : (				<div className='icon' style={getStyles({ size, color, src: state_src })} />
			)}		</div>
	)
}export default Index复制代码
// index.less._local {      display: flex;      :global {.icon_wrap {                  display: flex;                  justify-content: center;                  align-items: center;                  box-sizing: border-box;
            }.icon {                  vertical-align: middle;                  display: inline-block;                  background: black;                  mask-repeat: no-repeat;                  mask-size: cover;
            }
      }
}复制代码
// utils.tsimport { CSSProperties } from 'react'export const getWrapStyles = ({
	size,
	bordered,
	filled,
	round,
	borderColor,
	fillColor
}): CSSProperties => {	const styles: CSSProperties = {}	if (bordered) styles.border = '2rpx solid ' + borderColor	if (filled) styles.background = fillColor	if (round) styles.borderRadius = '50%'

	styles.width = size * 1.5 + 'px'
	styles.height = size * 1.5 + 'px'return styles
}export const getStyles = ({ size, color, src }): CSSProperties => {      let styles: CSSProperties = {}

	styles.width = size + 'px'
	styles.height = size + 'px'
	styles.background = color
	styles.WebkitMaskImage = `url("${src}")`return styles
}export const getSrc = (svg: string) => {	if (!svg) returnreturn 'data:image/svg+xml,' + svg.replace(/</g, '%3C').replace(/>/g, '%3E')
}复制代码

注意,目前这种方案仅针对svg,对于其他类型的图标并不适用,不过也足以应对大部分场景了。

最后,如果觉得文章对你有用,点赞,评论,让更多的人看到,谢谢。