canvas.js 8.25 KB
const cacheChart = {}
const fontSizeReg = /([\d\.]+)px/;
class EventEmit {
	constructor() {
		this.__events = {};
	}
	on(type, listener) {
		if (!type || !listener) {
			return;
		}
		const events = this.__events[type] || [];
		events.push(listener);
		this.__events[type] = events;
	}
	emit(type, e) {
		if (type.constructor === Object) {
			e = type;
			type = e && e.type;
		}
		if (!type) {
			return;
		}
		const events = this.__events[type];
		if (!events || !events.length) {
			return;
		}
		events.forEach((listener) => {
			listener.call(this, e);
		});
	}
	off(type, listener) {
		const __events = this.__events;
		const events = __events[type];
		if (!events || !events.length) {
			return;
		}
		if (!listener) {
			delete __events[type];
			return;
		}
		for (let i = 0, len = events.length; i < len; i++) {
			if (events[i] === listener) {
				events.splice(i, 1);
				i--;
			}
		}
	}
}
class Image {
	constructor() {
		this.currentSrc = null
		this.naturalHeight = 0
		this.naturalWidth = 0
		this.width = 0
		this.height = 0
		this.tagName = 'IMG'
	}
	set src(src) {
		this.currentSrc = src
		uni.getImageInfo({
			src,
			success: (res) => {
				this.naturalWidth = this.width = res.width
				this.naturalHeight = this.height = res.height
				this.onload()
			},
			fail: () => {
				this.onerror()
			}
		})
	}
	get src() {
		return this.currentSrc
	}
}
class OffscreenCanvas {
	constructor(ctx, com, canvasId) {
		this.tagName = 'canvas'
		this.com = com
		this.canvasId = canvasId
		this.ctx = ctx
	}
	set width(w) {
		this.com.offscreenWidth = w
	}
	set height(h) {
		this.com.offscreenHeight = h
	}
	get width() {
		return this.com.offscreenWidth || 0
	}
	get height() {
		return this.com.offscreenHeight || 0
	}
	getContext(type) {
		return this.ctx
	}
	getImageData() {
		return new Promise((resolve, reject) => {
			this.com.$nextTick(() => {
				uni.canvasGetImageData({
					x:0,
					y:0,
					width: this.com.offscreenWidth,
					height: this.com.offscreenHeight,
					canvasId: this.canvasId,
					success: (res) => {
						resolve(res)
					},
					fail: (err) => {
						reject(err)
					},
				}, this.com)
			})
		})
	}
}
export class Canvas {
	constructor(ctx, com, isNew, canvasNode={}) {
		cacheChart[com.canvasId] = {ctx}
		this.canvasId = com.canvasId;
		this.chart = null;
		this.isNew = isNew
		this.tagName = 'canvas'
		this.canvasNode = canvasNode;
		this.com = com;
		if (!isNew) {
			this._initStyle(ctx)
		}
		this._initEvent();
		this._ee = new EventEmit()
	}
	getContext(type) {
		if (type === '2d') {
			return this.ctx;
		}
	}
	setAttribute(key, value) {
		if(key === 'aria-label') {
			this.com['ariaLabel'] = value
		}
	}
	setChart(chart) {
		this.chart = chart;
	}
	createOffscreenCanvas(param){
		if(!this.children) {
			this.com.isOffscreenCanvas = true
			this.com.offscreenWidth = param.width||300
			this.com.offscreenHeight = param.height||300
			const com = this.com
			const canvasId = this.com.offscreenCanvasId
			const context = uni.createCanvasContext(canvasId, this.com)
			this._initStyle(context)
			this.children = new OffscreenCanvas(context, com, canvasId)
		} 
		return this.children
	}
	appendChild(child) {
		console.log('child', child)
	}
	dispatchEvent(type, e) {
		if(typeof type == 'object') {
			this._ee.emit(type.type, type);
		} else {
			this._ee.emit(type, e);
		}
		return true
	}
	attachEvent() {
	}
	detachEvent() {
	}
	addEventListener(type, listener) {
		this._ee.on(type, listener)
	}
	removeEventListener(type, listener) {
		this._ee.off(type, listener)
	}
	_initCanvas(zrender, ctx) {
		// zrender.util.getContext = function() {
		// 	return ctx;
		// };
		// zrender.util.$override('measureText', function(text, font) {
		// 	ctx.font = font || '12px sans-serif';
		// 	return ctx.measureText(text, font);
		// });
	}
	_initStyle(ctx, child) {
		const styles = [
			'fillStyle',
			'strokeStyle',
			'fontSize',
			'globalAlpha',
			'opacity',
			'textAlign',
			'textBaseline',
			'shadow',
			'lineWidth',
			'lineCap',
			'lineJoin',
			'lineDash',
			'miterLimit',
			// 'font'
		];
		const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
		styles.forEach(style => {
			Object.defineProperty(ctx, style, {
				set: value => {
					// if (style === 'font' && fontSizeReg.test(value)) {
					// 	const match = fontSizeReg.exec(value);
					// 	ctx.setFontSize(match[1]);
					// 	return;
					// }
					if (style === 'opacity') {
						ctx.setGlobalAlpha(value)
						return;
					}
					if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
						// #ifdef H5 || APP-PLUS || MP-BAIDU
						if(typeof value == 'object') {
							if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
								ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
							}
							return
						} 
						// #endif
						// #ifdef MP-TOUTIAO
						if(colorReg.test(value)) {
							value = value.replace(colorReg, '#$1$1$2$2$3$3')
						}
						// #endif
						ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
					}
				}
			});
		});
		if(!this.isNew && !child) {
			ctx.uniDrawImage = ctx.drawImage
			ctx.drawImage = (...a) => {
				a[0] = a[0].src
				ctx.uniDrawImage(...a)
			}
		}
		if(!ctx.createRadialGradient) {
			ctx.createRadialGradient = function() {
				return ctx.createCircularGradient(...[...arguments].slice(-3))
			};
		}
		// 字节不支持
		if (!ctx.strokeText) {
			ctx.strokeText = (...a) => {
				ctx.fillText(...a)
			}
		}
		// 钉钉不支持 
		if (!ctx.measureText) {
			const strLen = (str) => {
				let len = 0;
				for (let i = 0; i < str.length; i++) {
					if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
						len++;
					} else {
						len += 2;
					}
				}
				return len;
			}
			ctx.measureText = (text, font) => {
				let fontSize = ctx?.state?.fontSize || 12;
				if (font) {
					fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
				}
				fontSize /= 2;
				let isBold = fontSize >= 16;
				const widthFactor = isBold ? 1.3 : 1;
				return {
					width: strLen(text) * fontSize * widthFactor
				};
			}
		}
	}

	_initEvent(e) {
		this.event = {};
		const eventNames = [{
			wxName: 'touchStart',
			ecName: 'mousedown'
		}, {
			wxName: 'touchMove',
			ecName: 'mousemove'
		}, {
			wxName: 'touchEnd',
			ecName: 'mouseup'
		}, {
			wxName: 'touchEnd',
			ecName: 'click'
		}];

		eventNames.forEach(name => {
			this.event[name.wxName] = e => {
				const touch = e.touches[0];
				this.chart.getZr().handler.dispatch(name.ecName, {
					zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
					zrY: name.wxName === 'tap' ? touch.clientY : touch.y
				});
			};
		});
	}

	set width(w) {
		this.canvasNode.width = w
	}
	set height(h) {
		this.canvasNode.height = h
	}

	get width() {
		return this.canvasNode.width || 0
	}
	get height() {
		return this.canvasNode.height || 0
	}
	get ctx() {
		return cacheChart[this.canvasId]['ctx'] || null
	}
	set chart(chart) {
		cacheChart[this.canvasId]['chart'] = chart
	}
	get chart() {
		return cacheChart[this.canvasId]['chart'] || null
	}
}

export function dispatch(name, {x,y, wheelDelta}) {
	this.dispatch(name, {
		zrX: x,
		zrY: y,
		zrDelta: wheelDelta,
		preventDefault: () => {},
		stopPropagation: () =>{}
	});
}
export function setCanvasCreator(echarts, {canvas, node}) {
	// echarts.setCanvasCreator(() => canvas);
	if(echarts && !echarts.registerPreprocessor) {
		return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式')
	}
	echarts.registerPreprocessor(option => {
		if (option && option.series) {
			if (option.series.length > 0) {
				option.series.forEach(series => {
					series.progressive = 0;
				});
			} else if (typeof option.series === 'object') {
				option.series.progressive = 0;
			}
		}
	});
	function loadImage(src, onload, onerror) {
		let img = null
		if(node && node.createImage) {
			img = node.createImage()
			img.onload = onload.bind(img);
			img.onerror = onerror.bind(img);
			img.src = src;
			return img
		} else {
			img = new Image()
			img.onload = onload.bind(img)
			img.onerror = onerror.bind(img);
			img.src = src
			return img
		}
	}
	if(echarts.setPlatformAPI) {
		echarts.setPlatformAPI({
			loadImage: canvas.setChart ? loadImage : null,
			createCanvas(){
				const key = 'createOffscreenCanvas'
				return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
			}
		})
	}
}