Skip to content

canvas绘制标签阴影

效果图: 003.png

示例代码

vue
<template>
    <view class="content">
		<canvas class="page" canvas-id="myCanvas"></canvas>
		<div style="top: 20rpx;" class="line row"></div>
		<div style="top: 60rpx;" class="line row"></div>
		<div style="left: 20rpx;" class="line column"></div>
    </view>
</template>

<script setup>
	import { onReady } from '@dcloudio/uni-app'
    import Canvas from './canvas/index.js'
    onReady(() => {
        let ctx = new Canvas('myCanvas')
		
		let endX = ctx.draw_Tag('测试Tag', {
			top: 20,// 距离顶部距离(必填)
			left: 20,// 距离左侧距离(必填)
			height: 40,// 高度(必填)
			padding: 20,// 左右边距(必填)
			radius: [20],// 圆角 写法同正常样式的圆角一样
			fontSize: 12,// 字体大小(必填)
			fontColor: '#aa00ff',// 字体颜色(必填)
			fontStyle: 'sans-serif',// 字体样式(无需包含字体大小和行高,仅补充其他属性)
			bg: '#358aff',// 背景色
			// 渐变背景
			bg_linear: {
				deg: 0,
				colors: [{
						color: '#358aff',
						position: 0
					},
					{
						color: '#00ff00',
						position: 1
					},
				]
			},
			shadow: '4 10 12 rgba(0,0,0,0.6)',// 阴影
		})
		ctx.draw_Tag('测试Tag2', {
			top: 20,
			left: endX + 20,// 根据返回的结束X位置向右偏移20rpx
			height: 40,
			padding: 20,
			fontSize: 12,
			radius: [20, 10],
			bg: '#358aff',
			fontColor: '#FFFFFF',
			fontStyle: 'sans-serif',
			// shadow: '4 10 12 rgba(0,0,0,0.6)',
		})
    })
</script>
<style>
    .content {
		margin: 30rpx auto;
		height: 600rpx;
		width: 600rpx;
		position: relative;
		border: 1rpx solid red;
	}

	.page {
		width: 600rpx;
		height: 300rpx;
	}

	.line {
		background: rgba(0, 0, 0, .3);
		position: absolute;
		z-index: 10;
	}

	.column {
		width: 1px;
		height: 100%;
		top: 0;
	}

	.row {
		width: 100%;
		height: 1px;
		left: 0;
	}
</style>

canvas/index.js

js
let ratio = uni.getWindowInfo().windowWidth / 750
/**
 * @typedef {object} object_bg_linear 渐变背景
 * @property {number} deg 渐变角度(0-360)
 */
/**
 * @typedef {object} tagStyle
 * @property {number} top 到顶部的距离
 * @property {number} left 到左边的距离
 * @property {number} height 标签高度
 * @property {number} padding 左右边距
 * @property {number} fontSize 文字大小
 * @property {string.ColorString} bg 标签背景色(不传时会清空setFillStyle,导致tag背景色变为黑色)
 * @property {object_bg_linear} bg_linear 渐变标签
 * @property {number[]} radius 同css的border-radius写法类似,默认为[0,0,0,0]
 * @property {number} fontStyle CanvasContext.font 除了fontSize的其他配置
 */
export default class Canvas {
	/**
	 * @param {string} canvasId canvasId
	 */
	constructor(canvasId) {
		if (!canvasId) return Error('请传入canvasId')
		this.ctx = uni.createCanvasContext(canvasId)
	}
    /**
	 * 绘制Tag
	 * @param {string} text tag [必须]文本
	 * @param {tagStyle} style tag [必须]样式
	 * 
	 * @example  绘制两个tag,第二个根据第一个设置左侧边距
	 *  let canvas = new Canvas('myCanvas')
	 * // 绘制第一个tag 
	 *	let endX = canvas.draw_Tag('测试Tag',
	 *		{
				top:20,
				left:20,
				height:40,
				padding:20,
				fontSize:12,
				bg:'#358aff',
				bg_linear: {
					deg: 0,
					colors: [{
							color: '#358aff',
							position: 0
						},
						{
							color: '#00ff00',
							position: 1
						},
					]
				},
				fontColor:'#FFFFFF',
				radius:[20],
				fontStyle:'sans-serif',
				shadow:'4 10 12 rgba(0,0,0,.6)',
			}
		)
		canvas.draw_Tag('测试Tag2',
			{
				top:20,
				left:endX+20,
				height:40,
				padding:20,
				fontSize:12,
				radius:[20,10],
				bg:'#358aff',
				fontColor:'#FFFFFF',
				fontStyle:'sans-serif',
				shadow:'4 10 12 rgba(0,0,0,.6)',
			}
		)
	 */
	draw_Tag(text, style) {
		let {
			top,
			left,
			height,
			fontSize,
			fontColor,
			fontStyle,
			bg,
			padding=0,
			radius = [0, 0, 0, 0],
		} = style
		if (!top) {
			console.log(text + '-->[tagStyle]', style);
			console.error(text + '-->tagStyle.top错误: ', top);
		}
		if (!left) {
			console.log(text + '-->[tagStyle]', style);
			console.error(text + '-->tagStyle.left错误: ', left);
		}
		if (!height) {
			console.log(text + '-->[tagStyle]', style);
			console.error('tagStyle.height错误: '+'['+text+']', height);
		}
		// 设置字体样式 获取文本宽度
		this.setFont(fontSize * ratio, fontStyle)
		let width = this.ctx.measureText(text).width

		
		if (style.shadow) { // tag阴影设置
			let arr_shadow = style.shadow.split(' ').filter(item => item !== '')
			this.ctx.setShadow(Number(arr_shadow[0]) * ratio, Number(arr_shadow[1]) * ratio, Number(arr_shadow[2]) *
				ratio, arr_shadow[3])
		}
		switch(radius.length){
			case 1:
			radius.length = 4
			radius.fill(radius[0])
			break
			case 2:
			radius.push(radius[0])
			radius.push(radius[1])
			break;
			case 3:
			radius.push(radius[1])
			break
		}
		this.ctx.save()
		this.ctx.beginPath()
		radius.map((item, index) => {
			if (index == 0) {
				if (item == 0) {
					this.ctx.moveTo(left * ratio, top * ratio)
				} else {
					this.ctx.arc(
						(left + item) * ratio,
						(top + item) * ratio,
						item * ratio,
						Math.PI,
						1.5 * Math.PI
					)
				}
			}
			if (index == 1) {
				if (item == 0) {
					this.ctx.lineTo((left + padding * 2) * ratio + width, top * ratio)
				} else {
					this.ctx.arc(
						(left + padding * 2 - item) * ratio + width,
						(top + item) * ratio,
						item * ratio,
						1.5 * Math.PI,
						2 * Math.PI)
				}
			}
			if (index == 2) {
				if (item == 0) {
					this.ctx.lineTo((left + padding * 2) * ratio + width, (top + height) * ratio)
				} else {
					this.ctx.arc(
						(left + padding * 2 - item) * ratio + width,
						(top + height - item) * ratio,
						item * ratio,
						0,
						0.5 * Math.PI
					)
				}
			}
			if (index == 3) {
				if (item == 0) {
					this.ctx.lineTo(left * ratio, (top + height) * ratio)
				} else {
					this.ctx.arc(
						(left + item) * ratio,
						(top + height - item) * ratio,
						item * ratio,
						0.5 * Math.PI,
						Math.PI)
				}
			}
		})
		this.ctx.closePath()
		this.ctx.setFillStyle(bg)// bg_linear线性渐变需要裁剪区域,裁剪前绘制一次生成阴影
		this.ctx.fill()
		// 设置渐变样式
		if(style.bg_linear){
			this.ctx.clip()
			// 根据角度计算起始和终止点位坐标
			let sx = left
			let sy = top
			let ex = left
			let ey = top
			let deg = style.bg_linear.deg
			if(deg%360 == 0){
				sy += height
			}else if(deg==90){
				ex = left+padding*2+width/ratio
			}else if(deg==180){
				ey += height
			}else if(deg==270){
				sx = left+padding*2+width/ratio
			}else if(deg>0&&deg<90){
				sy += height
				ex = left+padding*2+width/ratio
				ey = Math.tan(deg)/height+top
			}else if(deg>90&&deg<180){
				ex = left+padding*2+width/ratio
				ey = Math.tan(deg)/height+height+top
			}else if(deg>180&&deg<270){
				sx = left+padding*2+width/ratio
				ey = Math.tan(deg)/height+height+top
			}else if(deg>180&&deg<270){
				sx = left+padding*2+width/ratio
				sy += height
				ey = Math.tan(deg)/height+top
			}else if(deg>270&&deg<360){
				sx = left+padding*2+width/ratio
				sy += height
				ey = Math.tan(deg)/left*-1 - top
			}
			console.log(`起始位置(${sx},${sy})`);
			console.log(`结束位置(${ex},${ey})`);
			let grd = this.ctx.createLinearGradient(
				sx*ratio,
				sy*ratio,
				ex*ratio,
				ey*ratio
			)
			style.bg_linear.colors.map(item=>{
				grd.addColorStop(item.position,item.color)
			})
			this.ctx.setFillStyle(grd)
			this.ctx.fill()
			this.ctx.restore()
		}
	
		if (style.shadow) { // 阴影清空
			this.ctx.setShadow(0, 0, 0, '')
			console.log('阴影清空: ',text);
		}
		// 绘制文本
		this.ctx.setFillStyle(fontColor)
		this.ctx.fillText(
			text,
			(left + padding) * ratio,
			(top + height / 1.65) * ratio
		)
		this.ctx.draw(true)

		return left + padding * 2 + width / ratio
	}
	setFont(fontSize, fontStyle) {
		this.ctx.font = `${fontSize}px/1 ${fontStyle}`
		this.ctx.setFontSize(fontSize)
	}
}