canvas实现图片平移,缩放的例子

最近有个水印预览的功能,使用 canvas 实现
效果如下
在这里插入图片描述

代码如下

原先配置是响应式的,提出来了就不显示操作了,模拟值都写死的 界面给大家参考阅读。

<!DOCTYPE html>
<html>

<head>
    <title>Canvas :平移和缩放</title>
</head>

<body>
    <div style="width:580px; height:440px">
        <canvas id="canvas"></canvas>
    </div>
    <script>
        class PreviewImage {
            el = null
            ctx = null
            image = null
            scale = 1
            translateX = 0
            translateY = 0
            dragging = 0
            drag_sx = 0
            drag_sy = 0
            ratio = window.devicePixelRatio || 1

            constructor(el, options = {}) {
                this.el = el
                this.options = options
                this.init()
            }

            init() {
                const { el, w, h } = this
                this.ctx = el.getContext('2d')
                el.width = w
                el.height = h
                this.createImage()
                this.bindEvent()
            }

            update(options) {
                this.options = options
                this.createImage()
            }

            bindEvent() {
                const { el } = this
                this.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 } = evt
                this.drag_sx = clientX
                this.drag_sy = clientY
                this.dragging = 1
                document.body.style.cursor = 'move'
                document.body.style.userSelect = 'none'
            }

            mouseup() {
                this.dragging = 0
                document.body.style.cursor = 'auto'
                document.body.style.userSelect = 'auto'
            }

            mousemove(evt) {
                const { clientX, clientY } = evt
                const { dragging, drag_sx, drag_sy } = this
                if (!dragging) return

                const dx = clientX - drag_sx
                const dy = clientY - drag_sy
                this.drag_sx = clientX
                this.drag_sy = clientY
                this.translate(dx, dy)
            }

            translate(dx, dy) {
                const { image } = this
                const { translateX, translateY } = this
                const { width, height } = image
                const x = translateX + dx
                const y = translateY + dy
                this.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()
            }

            get w() {
                return this.el.parentNode?.offsetWidth || 0
            }

            get h() {
                return this.el.parentNode?.offsetHeight - (30 + 45) || 0
            }

            async createImage() {
                const { ratio, options } = this
                try {
                    const img = await this.loadImage(options.src)
                    img.width = options.imageWidth
                    img.height = options.imageHeight
                    this.image = img
                    this.draw()
                } catch (error) {
                    console.error(error)
                }
            }

            wheel(evt) {
                evt.preventDefault()
                const { el } = this
                const { clientX, clientY, deltaY } = evt
                const x = clientX - el.offsetLeft
                const y = clientY - el.offsetTop

                const dampeningFactor = 0.05
                const minScale = 0.3
                const maxScale = 1.5
                const 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 } = this

                if (s < 1.02 && s > 0.98) s = 1

                const offsetX = (x - translateX) / s
                const offsetY = (y - translateY) / s
                this.translateX = x - offsetX * s
                this.translateY = y - offsetY * s
                this.image.width = options.imageWidth * ratio * s
                this.image.height = options.imageHeight * ratio * s
                this.scale = s
                this.draw()
            }

            draw() {
                const { ctx, ratio, w, h, image, translateX, translateY, scale } = this
                ctx.clearRect(0, 0, w, h)
                this.drawBackground()
                ctx.save()
                ctx.translate(translateX, translateY)
                const imageX = (w - image.width * scale * ratio) / 2
                const imageY = (h - image.height * scale * ratio) / 2
                ctx.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 } = this
                const { texts, textStyles } = options
                const { fontSize, fontFamily, fontColor, textAlign = 'center', lineX, lineY, lineAngle, textBaseline = 'top' } = textStyles
                ctx.font = `${fontSize * ratio * scale}px ${fontFamily || 'sans-serif'}`
                ctx.fillStyle = fontColor || '#303133'
                ctx.textAlign = textAlign
                ctx.textBaseline = textBaseline

                const text = this.transformText(texts)
                const imageX = (w - image.width * scale * ratio) / 2
                const imageY = (h - image.height * scale * ratio) / 2
                const posx = imageX + image.width * scale * ratio * (lineX / 100)
                const posy = imageY + image.height * scale * ratio * (lineY / 100)
                const angle = (lineAngle / 180) * Math.PI
                ctx.fillText(text, posx, posy)

            }

            scaling() {
                const { ctx, w, scale } = this
                if (scale < 1.03 && scale > 0.97) return
                ctx.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 } = this
                const posx = (0 / 100) * w * ratio
                const posy = (0 / 100) * h * ratio
                const width = (100 / 100) * w * ratio
                const height = (100 / 100) * h * ratio
                ctx.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 } = dialog
        const { fontSize, fontColor, fontFamily, lineX, lineY, lineAngle } = form
        const { images, active } = picture
        const 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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值