UNPKG

vue3-sketch-ruler

Version:

> In using vue3, the zoom operation used for page presentation

216 lines (198 loc) 6.61 kB
// 标尺中每小格代表的宽度(根据scale的不同实时变化) const getGridSize = (scale: number) => { if (scale <= 0.25) return 40 if (scale <= 0.5) return 20 if (scale <= 1) return 10 if (scale <= 2) return 5 if (scale <= 4) return 2 return 1 } export function setLast( x: number, value: number, width: number, height: number, ctx: CanvasRenderingContext2D, isHorizontal?: boolean ) { isHorizontal ? ctx.moveTo(x, 0) : ctx.moveTo(0, x) ctx.save() isHorizontal ? ctx.translate(x + 5, height * 0.2) : ctx.translate(width * 0.1, x + 32) if (!isHorizontal) ctx.rotate(-Math.PI / 2) // 旋转 -90 度 ctx.fillText(Math.round(value).toString(), 4, 7) ctx.restore() isHorizontal ? ctx.lineTo(x, height) : ctx.lineTo(width, x) ctx.stroke() ctx.closePath() ctx.setTransform(1, 0, 0, 1, 0, 0) } export function drawShadowText( x: number, y: number, num: number, ctx: CanvasRenderingContext2D, palette: any, isHorizontal?: boolean ) { ctx.fillStyle = palette.fontShadowColor ctx.strokeStyle = palette.longfgColor ctx.save() ctx.translate(x, y) if (!isHorizontal) ctx.rotate(-Math.PI / 2) // 设置字体为加粗 ctx.font = 'bold 12px Aria' // ctx.strokeText(String(num), 0, 0) ctx.fillText(String(num), 0, 0) ctx.restore() } export const drawCanvasRuler = ( ctx: CanvasRenderingContext2D, start: number, selectStart: number, selectLength: number, options: { scale: number width: number height: number ratio: number gridRatio: number palette: any canvasWidth: number canvasHeight: number showShadowText: boolean }, isHorizontal?: boolean //横向为true,纵向缺省 ) => { const { scale, width, height, ratio, palette, gridRatio, showShadowText } = options const { bgColor, fontColor, shadowColor, longfgColor } = palette const endNum = isHorizontal ? options.canvasWidth : options.canvasHeight ctx.setTransform(1, 0, 0, 1, 0, 0) // 还原,否则scale变大后会错乱 // 缩放ctx, 以简化计算 ctx.scale(ratio, ratio) ctx.clearRect(0, 0, width, height) // 1. 画标尺底色 ctx.fillStyle = bgColor ctx.fillRect(0, 0, width, height) const gridSize = getGridSize(scale) * gridRatio // 每小格表示的宽度 const gridSize10 = gridSize * 10 // 每大格表示的宽度 const gridPixel10 = gridSize10 * scale const startValue10 = Math.floor(start / gridSize10) * gridSize10 const offsetX10 = ((startValue10 - start) / gridSize10) * gridPixel10 // 长间隔起点刻度距离ctx原点(start)的px距离 const endValue = start + Math.ceil((isHorizontal ? width : height) / scale) // 终点刻度 // 2. 画阴影 if (selectLength) { const shadowX = (selectStart - start) * scale // 阴影起点坐标 const shadowWidth = selectLength * scale // 阴影宽度 ctx.fillStyle = shadowColor isHorizontal ? ctx.fillRect(shadowX, 0, shadowWidth, height) : ctx.fillRect(0, shadowX, width, shadowWidth) // 画阴影文字起始 if (showShadowText) { if (isHorizontal) { drawShadowText(shadowX, height * 0.4, Math.round(selectStart), ctx, palette, isHorizontal) const shadowEnd = (selectStart + selectLength - start) * scale drawShadowText( shadowEnd, height * 0.4, Math.round(selectStart + selectLength), ctx, palette, isHorizontal ) } else { drawShadowText(width * 0.4, shadowX, Math.round(selectStart), ctx, palette, isHorizontal) const shadowEnd = (selectStart + selectLength - start) * scale drawShadowText( width * 0.4, shadowEnd, Math.round(selectStart + selectLength), ctx, palette, isHorizontal ) } } } // 3. 画刻度和文字 ctx.beginPath() ctx.fillStyle = fontColor ctx.strokeStyle = longfgColor // 绘制长间隔和文字 for (let value = startValue10, count = 0; value < endValue; value += gridSize10, count++) { const x = offsetX10 + count * gridPixel10 + 0.5 // prevent canvas 1px line blurry if ((value - gridSize10 < endNum && value > endNum) || value == endNum) { // 如果尾数画最后一个刻度 const xl = offsetX10 + count * gridPixel10 + 0.5 + (endNum - value) * scale setLast(xl, endNum, width, height, ctx, isHorizontal) return } if (value >= 0 && value <= endNum) { if (value == 0) { if (isHorizontal) { ctx.moveTo(x, 0) ctx.lineTo(x, height) } else { ctx.moveTo(0, x) ctx.lineTo(width, x) } } else { if (isHorizontal) { ctx.moveTo(x, 20) ctx.lineTo(x, height / 1.3) } else { ctx.moveTo(20, x) ctx.lineTo(width / 1.3, x) } } ctx.save() // 影响文字位置 if (value == 0) { isHorizontal ? ctx.translate(x - 15, height * 0.01) : ctx.translate(width * 0.3, x - 5) } else { isHorizontal ? ctx.translate(x - 12, height * 0.05) : ctx.translate(width * 0.05, x + 12) } if (!isHorizontal) { ctx.rotate(-Math.PI / 2) // 旋转 -90 度 } // 如果最后一个大刻度挨着最后一个刻度, 不画文字 if (endNum - value > gridSize10 / 2) { if ( !showShadowText || selectLength == 0 || (Math.abs(value - selectStart) > gridSize10 / 2 && Math.abs(value - (selectStart + selectLength)) > gridSize10 / 2) ) { ctx.fillText(value.toString(), 4, 9) } } ctx.restore() } } ctx.stroke() ctx.closePath() } interface IDebounce { (): void cancel(): void } // 定义一个泛型类型,确保T是一个函数类型 type FunctionType<T = any, R = any> = (this: T, ...args: any[]) => R export function debounce<T extends FunctionType>(func: T, wait = 100): IDebounce { let timeout: ReturnType<typeof setTimeout> | null = null const debounced: IDebounce = function (...args: Parameters<T>) { if (timeout !== null) { clearTimeout(timeout) } timeout = setTimeout(() => { func(...args) }, wait) } debounced.cancel = function () { if (timeout !== null) { clearTimeout(timeout) timeout = null } } return debounced as IDebounce }