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

最近有个水印预览的功能,需要用到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 = 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()
            }

            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 } = 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.scale = s
                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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53740.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

element-tree-line el-tree 添加结构线 添加虚线

概览&#xff1a;给element组件添加上虚线&#xff0c;通过使用插件element-tree-line 参考连接&#xff1a; 参考别人的博客 安装插件&#xff1a; # npm npm install element-tree-line -S # yarn yarn add element-tree-line -S main.js全局注册引入插件&#xff1a; imp…

PCL点云处理之最小二乘空间直线拟合(3D) (二百零二)

PCL点云处理之最小二乘空间直线拟合(3D) (二百零二) 一、算法简介二、实现代码三、效果展示一、算法简介 对于空间中的这样一组点:大致呈直线分布,散乱分布在直线左右, 我们可采用最小二乘方法拟合直线,更进一步地,可以通过点到直线的投影,最终得到一组严格呈直线分布…

气象名词解释

文章目录 SAMPSAAMO SAM SAM(Southern Annualr Mode) 南半球环状模&#xff0c;是南半球大气环流和气候变异的一种重要现象。具有如下特点&#xff1a; 主要特点&#xff1a; 赤道附近环流&#xff1a;在 SAM 正相位期间&#xff0c;赤道附近的环流增强&#xff0c;称为正 SA…

使用多数据源dynamic-datasource-spring-boot-starter遇到的问题记录

记录使用多数据源dynamic-datasource-spring-boot-starter遇到的问题&#xff1a; 1、工程启动失败 缺少clickhouse连接驱动&#xff0c;引入对应的maven依赖 <!--ck连接驱动--><dependency><groupId>ru.yandex.clickhouse</groupId><artifactId>…

vue3搭建Arco design UI框架

技术&#xff1a;Vue3.2.40 UI框架&#xff1a;Arco design 2.44.7 需要安装:yarn 1.22.19 和npm 8.19.4 1.第一步安装本地全局arco脚手架 管理员运行CMD npm i -g arco-cli安装成功后如下&#xff1a; 2.第二步在需要存放项目的文件夹拉取项目 我这里把项目存放在 D:\W…

【Android】APP网络优化学习笔记

网络优化原因 进行网络优化对于移动应用程序而言非常重要&#xff0c;原因如下&#xff1a; 用户体验&#xff1a; 网络连接是移动应用程序的核心功能之一。通过进行网络优化&#xff0c;可以提高应用的加载速度和响应速度&#xff0c;减少用户等待时间&#xff0c;提供更流…

学习笔记|大模型优质Prompt开发与应用课(二)|第四节:大模型帮你写代码,小白也能做程序

文章目录 01软件开发产业趋势与技术革新软件开发产业趋势与技术革新技术性人才很受欢迎软件开发产业趋势与技术革新技术门槛越来越低 02 大模型驱动的软件开发需求分析prompt 产品设计开发和测试prompt输出回复promptpromptprompt回复 发布和部署promptprompt 维护和更新prompt…

BES2700 SDK绝对时间获取方法

1 代码 2 实验 log 需要换算下

垃圾焚烧设备PLC数据采集远程监控系统解决方案

PLC可以应用于各种污染废物处理设备的自动化控制&#xff0c;如污水处理、垃圾焚烧、空气处理等。例如&#xff0c;通过对垃圾焚烧PLC设备的数据采集&#xff0c;可以实现对垃圾焚烧的温度、时间、氧气流量等数据的远程监控和实时预警&#xff0c;有效提高垃圾焚烧效率和环保效…

最全的3D动画软件介绍来了!良心总结9款3D动画制作必备软件

现在&#xff0c;市面上流行着的3D动画软件如此之多&#xff0c;以至于很难敲定到底哪一款更适合自己或自己的团队。本篇文章带来了一些热门的、被视为行业标准的3D动画软件的介绍&#xff0c;帮助您更好地做出选择。 不仅如此&#xff0c;您还能从文章中了解到在数字内容创建…

day44-Custom Range Slider(自定义范围滑块)

50 天学习 50 个项目 - HTMLCSS and JavaScript day44-Custom Range Slider&#xff08;自定义范围滑块&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewp…

从 0 到 1!得物如何打造通用大模型训练和推理平台

1.背景 近期&#xff0c;GPT 大模型的发布给自然语言处理&#xff08;NLP&#xff09;领域带来了令人震撼的体验。随着这一事件的发生&#xff0c;一系列开源大模型也迅速崛起。依据一些评估机构的评估&#xff0c;这些开源模型大模型的表现也相当不错。一些大模型的评测情况可…

Git移除commit过的大文件

前言&#xff1a;在提交推送本地更改至仓库时&#xff0c;误将大文件给提交了&#xff0c;导致push时报错文件过大&#xff0c;因此需要将已经commit的大文件移除后再push 若已知要删除的文件或文件夹路径&#xff0c;则可以从第4步开始 1.对仓库进行gc操作 $ git gc 2.查询…

【使用深度学习的城市声音分类】使用从提取音频特征(频谱图)中提取的深度学习进行声音分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Helm KinD kubectl krew Istio急速安装

本篇更新网上许多安装失效的工具&#xff0c;如krew和KinD。 本篇测试使用时间为2023/7/20&#xff0c;基本都为最新版本或最新稳定版本。 前置 Helm 是 Kubernetes 的一个包管理工具&#xff0c;用于简化 Kubernetes 应用的部署和管理。Helm 使用名为 "chart" 的打…

Vue+Nodejs+Express+Minio 实现本地图片上传

安装Minio,Minio server和Minio client都要下载可以自定义安装目录 安装完成之后,可以将minio配置成环境变量方便使用 配置了环境变量启动命令式 minio server start,默认账号密码minioadmin和minioadmin,点击9000端口的这个链接,即可访问客户端 nodejs连接Minio,简易服务进…

CSDN 一周年创作纪念日(PS:vnjohn)

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

AWS——01篇(AWS入门 以及 AWS之EC2实例及简单实用)

AWS——01篇&#xff08;AWS入门 以及 AWS之EC2实例及简单实用&#xff09; 1. 前言2. 创建AWS账户3. EC23.1 启动 EC2 新实例3.1.1 入口3.1.2 设置名称 选择服务3.1.3 创建密钥对3.1.4 网络设置——安全组3.1.4.1 初始设置3.1.4.2 添加安全组规则&#xff08;开放新端口&…

Mindar.JS——实现AR图像追踪插入图片或视频

Mindar.JS使用方式 注意&#xff1a;此篇文章需要启动https才可调用相机权限 图像追踪示例 需要用到两个js库 <script src"./js/aframe.min.js"></script><script src"./js/mindar-image-aframe.prod.js"></script>下面看一下标签…

生成对抗网络DCGAN学习实践

在AI内容生成领域&#xff0c;有三种常见的AI模型技术&#xff1a;GAN、VAE、Diffusion。其中&#xff0c;Diffusion是较新的技术&#xff0c;相关资料较为稀缺。VAE通常更多用于压缩任务&#xff0c;而GAN由于其问世较早&#xff0c;相关的开源项目和科普文章也更加全面&#…