canvas实现对图片的标注(pc移动兼容),包括画任意线,画矩形,画圆,删除标注,撤销与反撤销,下载图片,改变画笔颜色,改变画笔大小

canvas实现对图片的标注(pc移动兼容),包括画任意线,画矩形,化圆,删除标注,撤销与反撤销,下载图片,改变画笔颜色,改变画笔大小

可以自己封装成组件使用
<template>
    <van-overlay :show="show" class="h-100% w-100% display-flex justify-center items-center">
        <div class=" h-auto">
            <canvas id="canvas" ref="canvasRef" width="100%" height="300px" @mousedown="canvasDown($event)"
                @mouseup="canvasUp($event)" @mousemove="canvasMove($event)" @touchstart="canvasDown($event)"
                @touchend="canvasUp($event)" @touchmove="canvasMove($event)">
            </canvas>
            <!-- <img :src="signSrc" alt="" class=" w-full" /> -->
            <div class="h-150px w-full bg-#fff">
                <div class="w-90% m-auto h-40px bg-#fff display-flex justify-around items-center flex-wrap">
                    <!-- 曲线 -->
                    <div class="icon-div icon" @click="penClick">
                        <i class="iconfont" style="user-select: none;">&#xe636</i>
                    </div>
                    <!-- 长方形 -->
                    <div class="icon-div icon" @click="rectangleClick">
                        <i class="iconfont " style="user-select: none;">&#xeb97</i>
                    </div>
                    <!---->
                    <div class="icon-div icon" @click="roundClick">
                        <i class="iconfont" style="user-select: none;">&#xeb99</i>
                    </div>
                    <!-- 左撤销 -->

                    <!-- 删除 -->
                    <div class="icon-div icon" @click="resetCanvas">
                        <i class="iconfont" style="user-select: none;">&#xe6a0</i>
                    </div>
                    <!-- 下载 -->
                    <div class="icon-div icon" @click="saveImg">
                        <i class="iconfont" style="user-select: none;"> &#xe8c6;</i>
                    </div>
                    <div class="icon-div icon" @click="revoke">
                        <i class="iconfont" style="user-select: none;" :class="zuo == true ? '' : 'grey-redo'"
                            scale="4">&#xe604</i>
                    </div>
                    <!-- 右撤销 -->
                    <div class="icon-div icon" @click="cancleRedo">
                        <i class="iconfont" style="user-select: none;" :class="you == true ? '' : 'grey-cancelRedo'"
                            scale="4">&#xe605</i>
                    </div>
                </div>
                <div class="icon-div icon">
                    <div class=" w-90% m-auto">
                        <div class="text-#000">画笔大小</div>
                        <input type="range" id="lwRange" min="1" max="10" value="1" @change="LwRangeBtn" />
                        <div class="text-#000 p-t-5px p-b-5px">画笔颜色</div>
                        <input type="color" id="lcolor" value="#FF0000" @change="LcolorBtn" />
                    </div>
                </div>
            </div>
            <!-- <div class="drawPane position-fixed top-62% left-15px" v-show="isShowDrawPane">
                <div @click="isShowDrawPane = false" class="float-right">
                    <i class="iconfont">&#xe69e</i>
                </div>
                <div class="text-#000">画笔大小</div>
                <input type="range" id="lwRange" min="1" max="10" value="1" @change="LwRangeBtn" />
                <div class="text-#000 p-t-5px p-b-5px">画笔颜色</div>
                <input type="color" id="lcolor" value="#FF1493" @change="LcolorBtn" />
            </div> -->
        </div>
    </van-overlay>
</template>
  
<script lang="ts">
// @ts-nocheck
export default defineComponent({
    name: "SignatureBoard",
    components: {},
    /* 接收父组件传过来的参数 */
    props: ['url'],
    setup(props, ctx) {
        const route = useRoute();
        const router = useRouter();
        const canvasRef = ref(null);
        const data = reactive({
            flag: false,
            imgUrl: props.url,
            img: new Image(),
            context: {} as any, // canvas对象
            oldX: 0, // 第一次点击X轴
            oldY: 0, // 第一次点击Y轴
            penDraw: false, // 画笔工具是否启用
            rectangleDraw: false, // 矩形工具是否启用
            roundDraw: false, // 圆形工具是否启用
            arrowDraw: false,
            signSrc: '', // 保存的图片地址
            show: true,
            imageData: '',
            cindex: 0,
            isShowDrawPane: false,
            // 撤销 
             history: [] as any, // 存储每次操作
            /*箭头是否为黑色 */
            zuo: false,
            you: false,
        });
        const methodsMap = {
            //这是默认的开始
            //   getUrl,
            init() {
                methodsMap.initDraw()
                methodsMap.penClick()
            },
            // 判断是PC端还是移动端
            isPc() {
                const userAgentInfo = navigator.userAgent
                const Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod']
                let flag = true
                for (let v = 0; v < Agents.length; v++) {
                    if (userAgentInfo.indexOf(Agents[v]) > 0) {
                        flag = false
                        break
                    }
                }
                return flag
            },
            // 初始化canvas
            initDraw() {
                data.img.crossOrigin = ''
                const canvas = document.getElementById("canvas")
                var width = window.screen.width;
                if (!canvas) {
                    return false
                } else {
                    data.context = canvas.getContext('2d')
                    data.img.src = data.imgUrl
                }
                data.img.onload = () => {
                    if (data.img.complete) {
                        if (methodsMap.isPc()) {
                            /* 图片再屏幕上的宽和高 */
                            canvas.setAttribute("width", '700')
                            canvas.setAttribute("height", (data.img.height * 700) / data.img.width)
                            //绘制图片
                            data.context.drawImage(data.img, 0, 0, '700', (data.img.height * 700) / data.img.width)
                        } else {
                            //根据图像重新设定canvas宽高
                            canvas.setAttribute('width', window.innerWidth)
                            canvas.setAttribute('height', (window.innerWidth * data.img.height) / data.img.width)
                            //绘制图片
                            data.context.drawImage(data.img, 0, 0, window.innerWidth, (window.innerWidth * data.img.height) / data.img.width)
                        }
                        data.context.lineWidth = 1,
                            data.context.strokeStyle = "red"
                    }
                }
            },
            // 画笔大小
            LwRangeBtn() {
                data.context.lineWidth = parseInt(document.getElementById("lwRange")?.value)
            },
            // 画笔颜色
            LcolorBtn() {
                data.context.fillStyle = document.getElementById("lcolor").value
                data.context.strokeStyle = document.getElementById("lcolor").value
                data.context.strokeStyle = document.getElementById("lcolor").value
            },
            // 点击画笔
            penClick() {
                data.penDraw = true
                data.rectangleDraw = false
                data.roundDraw = false
            },
            // 点击矩形
            rectangleClick() {
                data.penDraw = false
                data.rectangleDraw = true
                data.roundDraw = false
            },
            // 点击圆形
            roundClick() {
                data.penDraw = false
                data.rectangleDraw = false
                data.roundDraw = true
            },
           
            // 保存图片
            saveImg() {
                const canvas = document.querySelector('canvas')
                // location.href = this.$refs.canvas.toDataURL().replace('image/png', 'image/stream')
                const imgBase64 = canvasRef.value.toDataURL('image/png')
                data.img.crossOrigin = 'anonymous'
                const date = Date.now().toString()
                data.signSrc = imgBase64
                let aLink = document.createElement('a')
                let blob = this.base64ToBlob(imgBase64)
                let evt = document.createEvent('HTMLEvents')
                evt.initEvent('click', true, true)
                aLink.download = `${date}.jpg`
                aLink.href = URL.createObjectURL(blob)
                aLink.click()
            },
            // 图片转化
            base64ToBlob(code: any) {
                data.img.crossOrigin = ''
                let parts = code.split(';base64,')
                let contentType = parts[0].split(':')[1]
                let raw = window.atob(parts[1])
                let rawLength = raw.length
                let uInt8Array = new Uint8Array(rawLength)
                for (let i = 0; i < rawLength; ++i) {
                    uInt8Array[i] = raw.charCodeAt(i)
                }
                return new Blob([uInt8Array], { type: contentType })
            },
            // 任意画笔
            pencil(e: any) {
                const t = e.target
                if (methodsMap.isPc()) {
                    const newx = e.offsetX  //e.pageX
                    const newy = e.offsetY  //e.pageY
                    /*  添加一个新点,创建从该点到最后指定点的线条 */
                    data.context.lineTo(newx, newy)
                } else {
                    const newx = e.changedTouches[0].clientX - t.parentNode.offsetLeft
                    const newy = e.changedTouches[0].clientY - t.parentNode.offsetTop
                    data.context.lineTo(newx, newy)
                }
                /* 绘制已定义的路径 */
                data.context.stroke()
            },
            // 绘制矩形
            rectangle(e: any) {
                const t = e.target
                if (methodsMap.isPc()) {
                    const newX = e.offsetX  //e.pageX
                    const newY = e.offsetY  // e.pageY
                    /* 起始一条路径,或重置当前路径 */
                    data.context.beginPath()
                    data.context.rect(data.oldX, data.oldY, newX - data.oldX, newY - data.oldY)
                } else {
                    const newX = e.changedTouches[0].clientX - t.parentNode.offsetLeft
                    const newY = e.changedTouches[0].clientY - t.parentNode.offsetTop
                    // console.log(newX, newY);
                    data.context.beginPath()
                    data.context.rect(data.oldX, data.oldY, newX - data.oldX, newY - data.oldY)
                }
                data.context.stroke()
            },
            // 绘制圆形
            round(e: any) {
                const t = e.target
                if (methodsMap.isPc()) {
                    const newX = e.offsetX // e.pageX
                    const newY = e.offsetY //e.pageY
                    data.context.beginPath()
                    var r = Math.sqrt(Math.pow(newX - data.oldX, 2), Math.pow(newY - data.oldY), 2)
                    /*  ctx.arc(x, y, radius, start, end, Boolean)
                          圆心坐标: (x, y) 半径: radius
                          起始角度: start 结束角度: end
                          是否逆时针旋转: false 代表顺时针旋转 */
                    data.context.arc(data.oldX, data.oldY, r, 0, 2 * Math.PI)
                } else {
                    const newX = e.changedTouches[0].clientX - t.parentNode.offsetLeft
                    const newY = e.changedTouches[0].clientY - t.parentNode.offsetTop
                    data.context.beginPath()
                    var r = Math.sqrt(Math.pow(newX - data.oldX, 2), Math.pow((newY - data.oldY)), 2)
                    data.context.arc(data.oldX, data.oldY, r, 0, 2 * Math.PI)
                }
                data.context.closePath()
                data.context.stroke()
            },
            resetCanvas() {
                // 清空画布
                data.history.length = 0
                data.zuo = false
                data.you = false
                /* 在给定的矩形内清除指定的像素 */
                data.context.clearRect(0, 0, data.context.canvas.width, data.context.canvas.height)
                data.history.length = 0
                // 清空前后数据
                data.preDrawAry = []
                data.nextDrawAry = []
                // middleAry恢复到默认数据
                data.middleAry = []
                methodsMap.initDraw()
            },
            // 左撤销
            revoke() {
                if (data.cindex <= 0) return
                data.cindex--
                data.you = true
                data.context.putImageData(data.history[data.cindex], 0, 0)
                if (data.cindex <= 0) {
                    data.zuo = false
                }
            },
            // 右撤销
            cancleRedo() {
                if (data.cindex >= data.history.length - 1) return
                data.cindex++
                data.zuo = true
                data.context.putImageData(data.history[data.cindex], 0, 0)
                if (data.cindex >= data.history.length - 1) {
                    data.you = false
                }
            },
            // 鼠标落下
            canvasDown(e: any) {
                data.flag = true // 开始绘制
                // 获取鼠标起始位置
                if (this.isPc()) {
                    data.oldX = e.offsetX
                    data.oldY = e.offsetY
                } else {
                    data.oldX = e.changedTouches[0].clientX - e.target.parentNode.offsetLeft
                    data.oldY = e.changedTouches[0].clientY - e.target.parentNode.offsetTop
                }
                data.context.beginPath() // 分开路径,开始一个新的路径
                const preData = data.context.getImageData(0, 0, 600, 400)
                data.history.splice(data.cindex + 1)  
                data.zuo = true
            },
            // 鼠标滑动
            canvasMove(e: any) {
                if (data.flag === true) {
                   
                    if (data.history.length > 0) {
                        // history数组的长度大于0,才可以putImageData()
                        data.context.putImageData(
                            data.history[data.history.length - 1],
                            0,
                            0
                        )
                    } else {
                        data.history.push(data.context.getImageData(0, 0, 800, 600))
                    }
                    // 执行画笔
                    if (data.flag === true && data.penDraw === true) {
                        // console.log(123);
                        methodsMap.pencil(e)
                    }
                    // 执行矩形
                    if (data.flag === true && data.rectangleDraw === true) {
                        methodsMap.rectangle(e)
                    }
                    // 执行圆形
                    if (data.flag === true && data.roundDraw === true) {
                        methodsMap.round(e)
                    }
                }
            },
            // 鼠标抬起
            canvasUp(e: any) {
                const canvas = document.querySelector('canvas')
                const preData = data.context.getImageData(0, 0, 600, 400)
                data.imageData = data.context.getImageData(0, 0, canvas.width, canvas.height)
                data.history.push(data.context.getImageData(0, 0, 800, 600))
                data.cindex = data.history.length - 1
                data.flag = false
            },
        };
        onMounted(() => {
            methodsMap.init();
        });
        // watch()
        return {
            ...methodsMap,
            ...toRefs(data),
            canvasRef
        };
    },
});
</script>

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Canvas 上的图片标注、缩放、移动、保存历史状态、橡皮擦等功能,可以通过以下步骤来完成: 1. 加载图片。使用 JavaScript 中的 Image 对象加载图片,并在图片加载完成后将其绘制到 Canvas 上。 ```javascript const img = new Image(); // 创建 Image 对象 img.onload = function() { ctx.drawImage(img, 0, 0); // 将图片绘制到布上 }; img.src = 'image.jpg'; // 设置图片路径 ``` 2. 实现标注功能。通过鼠标事件监听用户的操作,使用 Canvas 的 API 绘制标注。例如,监听鼠标点击事件,在点击位置绘制一个圆形。 ```javascript canvas.addEventListener('mousedown', function(e) { ctx.beginPath(); ctx.arc(e.offsetX, e.offsetY, 5, 0, 2 * Math.PI); ctx.fill(); }); ``` 3. 实现缩放和移动功能。通过鼠标滚轮事件监听用户的操作,使用 Canvas 的 API 实现缩放和移动。例如,监听鼠标滚轮事件,在滚轮滚动时根据滚动方向调整布的缩放比例。 ```javascript canvas.addEventListener('wheel', function(e) { const delta = e.deltaY > 0 ? 0.1 : -0.1; const scale = Math.max(0.1, Math.min(10, currentScale + delta)); ctx.scale(scale, scale); currentScale = scale; }); ``` 4. 实现保存历史状态功能。使用 JavaScript 中的数组来保存历史状态,每当用户进行操作时,将当前状态保存到数组中。撤销操作时,从数组中取出上一个状态并恢复到布上。 ```javascript const states = []; // 保存历史状态的数组 function saveState() { states.push(canvas.toDataURL()); // 保存当前状态 } function undo() { if (states.length > 0) { const img = new Image(); img.onload = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空布 ctx.drawImage(img, 0, 0); // 绘制上一个状态 }; img.src = states.pop(); // 取出上一个状态 } } ``` 5. 实现橡皮擦功能。通过监听鼠标事件,在鼠标位置绘制一个与背景相同的矩形,来模拟橡皮擦的效果。 ```javascript canvas.addEventListener('mousemove', function(e) { if (erasing) { ctx.fillStyle = '#ffffff'; // 设置橡皮擦颜色为白色 ctx.fillRect(e.offsetX - 5, e.offsetY - 5, 10, 10); // 绘制矩形 } }); ``` 完整示例代码如下: ```html <!DOCTYPE html> <html> <body> <canvas id="canvas" width="600" height="400"></canvas> <button onclick="undo()">撤销</button> <button onclick="erasing = !erasing">橡皮擦</button> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let currentScale = 1; let erasing = false; const states = []; const img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0); saveState(); }; img.src = 'image.jpg'; canvas.addEventListener('mousedown', function(e) { ctx.beginPath(); ctx.arc(e.offsetX, e.offsetY, 5, 0, 2 * Math.PI); ctx.fill(); saveState(); }); canvas.addEventListener('mousemove', function(e) { if (erasing) { ctx.fillStyle = '#ffffff'; ctx.fillRect(e.offsetX - 5, e.offsetY - 5, 10, 10); } }); canvas.addEventListener('wheel', function(e) { const delta = e.deltaY > 0 ? 0.1 : -0.1; const scale = Math.max(0.1, Math.min(10, currentScale + delta)); ctx.scale(scale, scale); currentScale = scale; }); function saveState() { states.push(canvas.toDataURL()); } function undo() { if (states.length > 1) { const img = new Image(); img.onload = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); }; img.src = states[states.length - 2]; states.pop(); } } </script> </body> </html> ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值