最近有个水印预览的功能,需要用到canvas 绘制,canvas用的不是很熟,配合chatAI 完成功能。
效果如下
代码如下
原先配置是响应式的,提出来了就不显示操作了,模拟值都写死的 界面给大家参考阅读。
<!DOCTYPE html>
<html><head><title>Canvas :平移和缩放</title>
</head><body><div style="width:580px; height:440px"><canvas id="canvas"></canvas></div><script>class PreviewImage {el = nullctx = nullimage = nullscale = 1translateX = 0translateY = 0dragging = 0drag_sx = 0drag_sy = 0ratio = window.devicePixelRatio || 1constructor(el, options = {}) {this.el = elthis.options = optionsthis.init()}init() {const { el, w, h } = thisthis.ctx = el.getContext('2d')el.width = wel.height = hthis.createImage()this.bindEvent()}update(options) {this.options = optionsthis.createImage()}bindEvent() {const { el } = thisthis.mousedownBound = this.mousedown.bind(this)this.mousemoveBound = this.mousemove.bind(this)this.mouseupBound = this.mouseup.bind(this)this.wheelBound = this.wheel.bind(this)el.addEventListener('mousedown', this.mousedownBound, false)document.addEventListener('mouseup', this.mouseupBound, false)document.addEventListener('mousemove', this.mousemoveBound, false)el.addEventListener('wheel', this.wheelBound, false)}mousedown(evt) {const { clientX, clientY } = evtthis.drag_sx = clientXthis.drag_sy = clientYthis.dragging = 1document.body.style.cursor = 'move'document.body.style.userSelect = 'none'}mouseup() {this.dragging = 0document.body.style.cursor = 'auto'document.body.style.userSelect = 'auto'}mousemove(evt) {const { clientX, clientY } = evtconst { dragging, drag_sx, drag_sy } = thisif (!dragging) returnconst dx = clientX - drag_sxconst dy = clientY - drag_sythis.drag_sx = clientXthis.drag_sy = clientYthis.translate(dx, dy)}translate(dx, dy) {const { image } = thisconst { translateX, translateY } = thisconst { width, height } = imageconst x = translateX + dxconst y = translateY + dythis.translateX = Math.min(Math.max(x, width * 0.1 - width), width - width * 0.1)this.translateY = Math.min(Math.max(y, height * 0.1 - height), height - height * 0.1)this.draw()}wheel(evt) {evt.preventDefault()const { el } = thisconst { clientX, clientY, deltaY } = evtconst x = clientX - el.offsetLeftconst y = clientY - el.offsetTopconst dampeningFactor = 0.05const minScale = 0.3const maxScale = 1.5const scale = 1.0 + dampeningFactor * (deltaY > 0 ? -1 : 1)const currentScale = Math.min(Math.max(this.scale * scale, minScale), maxScale)this.zoom(currentScale, x, y)}zoom(s, x, y) {const { translateX, translateY } = thisif (s < 1.02 && s > 0.98) s = 1const offsetX = (x - translateX) / sconst offsetY = (y - translateY) / sthis.translateX = x - offsetX * sthis.translateY = y - offsetY * sthis.scale = sthis.draw()}get w() {return this.el.parentNode?.offsetWidth || 0}get h() {return this.el.parentNode?.offsetHeight - (30 + 45) || 0}async createImage() {const { ratio, options } = thistry {const img = await this.loadImage(options.src)img.width = options.imageWidthimg.height = options.imageHeightthis.image = imgthis.draw()} catch (error) {console.error(error)}}wheel(evt) {evt.preventDefault()const { el } = thisconst { clientX, clientY, deltaY } = evtconst x = clientX - el.offsetLeftconst y = clientY - el.offsetTopconst dampeningFactor = 0.05const minScale = 0.3const maxScale = 1.5const scale = 1.0 + dampeningFactor * (deltaY > 0 ? -1 : 1)const currentScale = Math.min(Math.max(this.scale * scale, minScale), maxScale)this.zoom(currentScale, x, y)}zoom(s, x, y) {const { translateX, translateY, options, ratio } = thisif (s < 1.02 && s > 0.98) s = 1const offsetX = (x - translateX) / sconst offsetY = (y - translateY) / sthis.translateX = x - offsetX * sthis.translateY = y - offsetY * sthis.image.width = options.imageWidth * ratio * sthis.image.height = options.imageHeight * ratio * sthis.scale = sthis.draw()}draw() {const { ctx, ratio, w, h, image, translateX, translateY, scale } = thisctx.clearRect(0, 0, w, h)this.drawBackground()ctx.save()ctx.translate(translateX, translateY)const imageX = (w - image.width * scale * ratio) / 2const imageY = (h - image.height * scale * ratio) / 2ctx.drawImage(image, imageX, imageY, image.width * scale, image.height * scale)this.drawTexts()ctx.restore()this.scaling()}drawTexts() {const { ctx, ratio, image, options, scale, w, h } = thisconst { texts, textStyles } = optionsconst { fontSize, fontFamily, fontColor, textAlign = 'center', lineX, lineY, lineAngle, textBaseline = 'top' } = textStylesctx.font = `${fontSize * ratio * scale}px ${fontFamily || 'sans-serif'}`ctx.fillStyle = fontColor || '#303133'ctx.textAlign = textAlignctx.textBaseline = textBaselineconst text = this.transformText(texts)const imageX = (w - image.width * scale * ratio) / 2const imageY = (h - image.height * scale * ratio) / 2const posx = imageX + image.width * scale * ratio * (lineX / 100)const posy = imageY + image.height * scale * ratio * (lineY / 100)const angle = (lineAngle / 180) * Math.PIctx.fillText(text, posx, posy)}scaling() {const { ctx, w, scale } = thisif (scale < 1.03 && scale > 0.97) returnctx.save()ctx.font = `12px xsans-serif`ctx.fillStyle = '#303133'ctx.textAlign = 'center'ctx.textBaseline = 'top'const text = `${(scale * 100).toFixed(0)}%`ctx.fillText(text, w - ctx.measureText(text).width + 10, 10)ctx.restore()}loadImage(url) {return new Promise((resolve, reject) => {const image = new Image()image.onload = () => {resolve(image)}image.onerror = () => {reject(new Error('无法加载图片: ' + url))}image.src = url})}drawBackground() {const { ctx, ratio, w, h } = thisconst posx = (0 / 100) * w * ratioconst posy = (0 / 100) * h * ratioconst width = (100 / 100) * w * ratioconst height = (100 / 100) * h * ratioctx.beginPath()ctx.fillStyle = '#F2F6FC'ctx.fillRect(posx, posy, width, height)}transformText(arr) {arr = Array.isArray(arr) ? arr : [arr]const keywods = {'${timestamp}': new Date().toLocaleString(),'${consumerName}': '消费者名称','${terminalIP}': '127.0.0.1',}return arr.join('-').replace(/\$\{timestamp\}|\$\{consumerName\}|\$\{terminalIP\}/g, matched => keywods[matched])}destroy() {this.el.removeEventListener('mousedown', this.mousedownBound)document.removeEventListener('mousemove', this.mousemoveBound)document.removeEventListener('mouseup', this.mouseupBound)this.el.removeEventListener('wheel', this.wheelBound)}}var dialog = {visible: false,predefine: ['#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585', '#cd5c5c', '#000000', '#ffffff'],title: '新增',controlled_width: 800,controlled_height: 600,form: {name: '',content: ['127.0.0.1'],lineX: 50,lineY: 10,lineAngle: 0,fontSize: 25,fontColor: '#ff4500',fontFamily: '',},}var picture = {images: ['https://images.pexels.com/photos/1784914/pexels-photo-1784914.jpeg?auto=compress&cs=tinysrgb&w=1600'],active: 0,}const { controlled_width, controlled_height, form } = dialogconst { fontSize, fontColor, fontFamily, lineX, lineY, lineAngle } = formconst { images, active } = pictureconst textStyles = {fontSize,fontColor,fontFamily,lineX,lineY,lineAngle,}const options = {src: images[active],imageWidth: controlled_width,imageHeight: controlled_height,texts: dialog.form.content,textStyles,}var canvas = document.getElementById('canvas')var priviewImage = new PreviewImage(canvas, options)</script>
</body></html>