canvas跟随鼠标画有透明度的椭圆边框

提示:canvas跟随鼠标画有透明度的椭圆边框

文章目录

  • 前言
  • 一、跟随鼠标画有透明度的椭圆边框
  • 总结


前言

一、跟随鼠标画有透明度的椭圆边框

test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas跟随鼠标移动画透明线</title>
    <style>
        div,canvas,img{
            user-select: none;
        }
        .my_canvas,.bg_img{
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
        }
        .cf{
            content: '';
            display: block;
            overflow: hidden;
            clear: both;
        }
        .fl{
            float: left;
        }
        .fr{
            float: right;
        }
        .bg_img{
            width: 674px;
            height: 495px;
            background: #ddd;
        }
        .img_tools{
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            border: 1px solid #eee;
            border-radius: 64px;
            height: 64px;
            line-height: 64px;
            box-sizing: border-box;
            padding: 15px 20px 0;
        }
        .img_tool{
           height: 32px;
           line-height: 32px;
           color: #000;
           font-size: 14px;
           text-align: center;
           width: 80px;
           border: 1px solid #ddd;
           border-radius: 32px;
           margin-right: 10px;
           cursor: pointer;
        }
        .img_tool_active{
            color: #409EFF;
            border: 1px solid #409EFF;
        }
    </style>
</head>
<body>
    <div class="bg_img"></div>
    <canvas id="myCanvasBot" class="my_canvas" width="674" height="495"></canvas>
    <canvas id="myCanvasTop" class="my_canvas" width="674" height="495"></canvas>
    <div class="img_tools cf">
        <div class="img_tool img_tool_active fl" onclick="changeType('curve',this)">涂鸦</div>
        <div class="img_tool fl" onclick="changeType('line',this)">直线</div>
        <div class="img_tool fl" onclick="changeType('rect',this)">矩形</div>
        <div class="img_tool fl" onclick="changeType('ellipse',this)">圆形</div>
        <div class="img_tool fl" onclick="changeType('eraser',this)">橡皮擦</div>
    </div>
    <script>
        const canvasWidth = 674;
        const canvasHeight = 495;
        //底层canvas
        const botCan = document.getElementById('myCanvasBot');
        //顶层canvas
        const topCan = document.getElementById('myCanvasTop');
        //底层画布
        const botCtx = botCan.getContext('2d');
        //顶层画布
        const topCtx = topCan.getContext('2d');
        topCtx.lineCap = 'round';
        topCtx.lineJoin = 'round';
        //鼠标是否按下  是否移动
        let isDown = false,isMove = false;
        //鼠标是否在canvas上抬起
        let isCanUp = false;
        //需要画图的轨迹
        let drawPoints = [];
        //起始点x,y
        let startPoint = {
            x:0,
            y:0
        };
        //图片历史
        let imgHistory = [];
        //icon历史
        // let partHistory = [];
        //操作类型
        let drawType = 'curve';
        //画线宽度
        const lineWidth = 10;
        //鼠标按下
        const mousedown = (e)=>{
            isDown = true;
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            startPoint = {x,y};
            drawPoints.push([{x,y}]);
            topCtx.strokeStyle = 'rgba(255,0,0,0.2)';
            topCtx.lineWidth = lineWidth;
            topCtx.beginPath();
            topCtx.moveTo(x,y);
        }
        //鼠标移动
        const mousemove = (e)=>{
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            if(isDown){
                isMove = true;
                switch(drawType){
                    case 'curve':
                        drawCurve(x,y);
                        break;
                    case 'line':
                        drawLine(x,y);
                        break;
                    case 'eraser':
                        drawEraser(x,y);
                        break;
                    case 'rect':
                        drawRect(x,y);
                        break;
                    case 'ellipse':
                        drawEllipse(x,y);
                        break;
                        
                }
            }
        }
        //鼠标抬起
        const mouseup = (e)=>{
            isCanUp = true;
            if(isDown){
                // topCan内容画到botCan上
                topToBot();
            }
        }
        //topCan内容画到botCan上
        const topToBot = ()=>{
            //把topCan画布生成图片
            let img = new Image();
            img.src = topCan.toDataURL('image/png');
            img.onload = ()=>{
                // partHistory.push(img);
                //添加到botCtx画布
                botCtx.drawImage(img,0,0);
                let historyImg = new Image();
                historyImg = botCan.toDataURL('image/png');
                historyImg.onload = ()=>{
                    //添加到历史记录
                    imgHistory.push(historyImg);
                }
                //清除topCtx画布
                topCtx.clearRect(0,0,canvasWidth,canvasHeight);
                //botCan画完之后,重置canvas的mouseup isCanUp
                if(isCanUp)isCanUp=false;
            }
            drawPoints = [];
            isDown = false;
            isMove = false;
        }
        //画椭圆形
        const drawEllipse = (x,y)=>{
            //清除topCtx画布
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            topCtx.beginPath();
            // 椭圆
            topCtx.ellipse((x+startPoint.x)/2, (y+startPoint.y)/2, Math.abs((x-startPoint.x)/2), Math.abs((y-startPoint.y)/2),0,0, Math.PI*2,true);
            topCtx.stroke();
        }
        //画矩形
        const drawRect = (x,y)=>{
            //清除topCtx画布
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            topCtx.beginPath();
            // 矩形
            topCtx.rect(startPoint.x, startPoint.y, x-startPoint.x, y - startPoint.y);
            topCtx.stroke();
        }
        //橡皮擦
        const drawEraser = (x,y)=>{
            //橡皮擦圆形半径
            const radius = lineWidth/2;
            botCtx.beginPath(); 
            for(let i=0;i<radius*2;i++){
                //勾股定理高h
                let h = Math.abs( radius - i); //i>radius h = i-radius; i<radius  h = radius - i
                //勾股定理l
                let l = Math.sqrt(radius*radius -h*h); 
                //矩形高度
                let rectHeight = 1;
                 //矩形宽度
                let rectWidth = 2*l;
                //矩形X
                let rectX = x-l;
                //矩形Y
                let rectY = y-radius + i;

                botCtx.clearRect(rectX, rectY, rectWidth, rectHeight);
            }
        }
        //画透明度直线
        const drawLine = (x,y)=>{
            if(!isDown)return;
            //清空当前画布内容
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            //必须每次都beginPath  不然会卡
            topCtx.beginPath();
            topCtx.moveTo(startPoint.x,startPoint.y);
            topCtx.lineTo(x,y);
            topCtx.stroke();
        }
        //画带透明度涂鸦
        const drawCurve = (x,y)=>{
            drawPoints.push({x,y});
            //清空当前画布内容
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            //必须每次都beginPath  不然会卡
            topCtx.beginPath();
            topCtx.moveTo(drawPoints[0].x,drawPoints[0].y);
            for(let i=1;i<drawPoints.length;i++){
                topCtx.lineTo(drawPoints[i].x,drawPoints[i].y);
            }
            topCtx.stroke();
        }
        //切换操作
        const changeType = (type,that)=>{
            if(drawType == type) return;
            let tools = document.getElementsByClassName('img_tool');
            for(let i=0;i<tools.length;i++){
                let ele = tools[i];
                if(ele.classList.contains('img_tool_active'))ele.classList.remove('img_tool_active'); //ele.removeClassName('img_tool_active');
            }
            that.classList.add('img_tool_active');
            drawType = type;
        }

        //canvas添加鼠标事件
        topCan.addEventListener('mousedown',mousedown);
        topCan.addEventListener('mousemove',mousemove);
        topCan.addEventListener('mouseup',mouseup);
        //全局添加鼠标抬起事件
        document.addEventListener('mouseup',(e)=>{
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            if(!isCanUp){
                if(drawType == 'line'){
                    let clientX = topCan.getBoundingClientRect().x;
                    let clientY = topCan.getBoundingClientRect().y;
                    drawLine(x-clientX,y-clientY);
                }
                // topCan内容画到botCan上
                topToBot();
            }
        });
        //全局添加鼠标移动事件
        document.addEventListener('mousemove',(e)=>{
            if(isMove)return isMove = false;
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            if(drawType == 'line'){
                let clientX = topCan.getBoundingClientRect().x;
                let clientY = topCan.getBoundingClientRect().y;
                drawLine(x-clientX,y-clientY);
            }
        });
    </script>
</body>
</html>

在这里插入图片描述

总结

踩坑路漫漫长@~@

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

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

相关文章

Ps:必须知道的四大混合模式

在 Photoshop 中有多达六组 27 种混合模式&#xff0c;不仅可应用于图层之间&#xff0c;在画笔等工具、图层样式等里面也都有&#xff0c;几乎可以说是无处不在。 有关混合模式的原理及说明&#xff0c;请参阅&#xff1a; 《Ps 混合模式&#xff08;合集&#xff09;》 在所有…

RN封装的底部向上弹出的弹出层组件

组件代码 import React from react; import { View, StyleSheet, Modal, TouchableOpacity, Text, TouchableWithoutFeedback } from react-native;const BottomPopup ({ visible, onClose, children, leftButtonTitle, rightButtonTitle, onLeftButtonPress, onRightButtonP…

day5-QT

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QFontDialog> //字体对话框类 #include<QFont> //字体类 #include<QMessageBox> //消息对话框类 #include<QColorDialog> //颜色对话框类 #include<QColor> //颜…

sqlite3嵌入式开发板命令行方式使用

如何在编译嵌入式版本的sqlite3&#xff0c;请看我上一篇文章 sqlite3 交叉编译-CSDN博客 一、sqlite3命令行方式使用 假如我将编译好的嵌入式的sqlite3放置在如下路径&#xff1a; 进入bin目录进行操作 1.运行sqlite3 运行sqlite3有两种方式 1&#xff09;直接在内存里面…

使用npm i进行admin依赖安装的时候出现问题

提示&#xff1a; npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/string-width failed, reason: certificate has expired 切换淘宝源到http或者更换其他国内镜像 npm config set registry http:/…

vue2 export default写法,computed、methods的使用

<template><div><h2>{{nameAll}}</h2><h2>{{method}}</h2><h2>{{tt()}}</h2><h2>{{firstName}}</h2><h2>更新后赋值数据&#xff1a;{{lastName}}</h2><h2>赋值数据:{{writeValue}}</h2>…

张驰咨询:光伏产业新质生产力提升咨询方案

光伏产业新质生产力提升咨询方案 一、光伏行业目前发展现状及特点 1、高度竞争 2、技术驱动 3、绿色发展 二、光伏发展新质生产力面临的痛点 1、成本压缩与效率提升并存挑战 2、新技术应用与推广难度 3、国际贸易摩擦影响 4、市场需求波动大 5、政策与补贴依赖性 三、…

C++初步认识

一.开发环境 Dev-C是一个Windows环境下适合于初学者使用的轻量级C/C 集成开发环境&#xff08;IDE&#xff09;&#xff0c;它是一款自由软件&#xff0c;遵守GPL许可协议分发源代码。 Dev-C 使用MingW64/TDM-GCC编译器&#xff0c;遵循C 11标准&#xff0c;同时兼容C98标准。开…

排序(冒泡/快速/归并)

冒泡排序 时间复杂度为 O(n^2) 原理 比较相邻的元素. 如果第一个比第二个大,就交换他们两个.依次比较每一对相邻的元素—>结果 : 最后的元素是这组数中最大的重复上述步骤 , 除了最后一个[]因为最后一个已经是排好序的了(这组数中最大的那个)]持续对越来越少的元素进行如上…

如何备考2025年AMC8竞赛?吃透2000-2024年600道真题(免费送题)

最近有家长朋友问我&#xff0c;现在有哪些类似于奥数的比赛可以参加&#xff1f;我的建议可以关注下AMC8的竞赛&#xff0c;类似于国内的奥数&#xff0c;但是其难度要比国内的奥数低一些&#xff0c;而且比赛门槛更低&#xff0c;考试也更方便。比赛的题目尤其是应用题比较有…

面试经典150题【101-110】

文章目录 面试经典150题【101-110】9.回文数61.加一172.阶乘后的069.x的平方根50.Pow(x,n)149.直线上最多的点数52.N皇后II120.三角形最小路径和64.最小路径和63.不同路径II 面试经典150题【101-110】 6道偏数学的题和4道二维dp 9.回文数 一开始想转为字符串再判断。后来发现…

1.5T数据惨遭Lockbit3.0窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件93起&#xff0c;近三周攻击数量呈现持平状态。 本周Lockbit3.0是影响最严重的勒索家族&#xff0c;Blacksuit和Ransomhub恶意家族紧随其后&#xff0c;从整体上看Lockbit3.0依旧是影响最严重的勒索家族&#xff0c;需要注意防范。 …

【python】flask模板渲染引擎Jinja2,通过后端数据渲染前端页面

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

基于SpringBoot和Leaflet的行政区划地图掩膜效果实战

目录 前言 一、掩膜小知识 1、GIS掩膜的实现原理 2、图层掩膜流程 二、使用插件 1、leaflet-mask介绍 2、核心代码解释 三、完整实例实现 1、后台逻辑实现 2、省级行政区划查询实现 3、行政区划定位及掩膜实现 4、成果展示 总结 前言 在之前的博客提过按空间矢量…

web服务应用术语

一、HTTP 协议详解 TCP 协议与 HTTP 协议 TCP 协议主要用于数据传输控制&#xff0c;而 HTTP 协议主要用于应用层面的数据交互。 HTTP 属于应用层协议&#xff0c;是建立在 TCP 协议基础之上的&#xff0c;HTTP 协议以客户端请求和服务器端响应为标准&#xff0c;浏览器通常称…

智慧公厕,让数据和技术更好服务社会生活

智慧公厕&#xff0c;作为智慧城市建设中不可忽视的一部分&#xff0c;正逐渐受到越来越多人的关注。随着科技的不断进步&#xff0c;智能化公厕已经成为一种趋势&#xff0c;通过数据的流转和技术的整合&#xff0c;为社会生活带来了更好的服务。本文以智慧公厕源头实力厂家广…

C#学习笔记3:Windows窗口计时器

今日继续我的C#学习之路&#xff0c;今日学习自己制作一个Windows窗口计时器程序&#xff1a; 文章提供源码解释、步骤操作、整体项目工程下载 完成后的效果大致如下&#xff1a;&#xff08;可选择秒数&#xff0c;有进度条&#xff0c;开始计时按钮等&#xff09; &#xf…

宝塔面板操作一个服务器域名部署多个网站

此处记录IP一样&#xff0c;端口不一样的操作方式&#xff1a; 宝塔面板操作&#xff1a; 1、创建第一个网站&#xff1a; 网站名用IP地址&#xff0c;默认80端口。 创建好后&#xff0c;直接IP访问就可以了。看到自带的默认首页 2、接下来部署第二个网站&#xff1a; 仍然是…

HCIP—BGP路由发布

R1和R2&#xff0c;R4和R5建立EBGP对等体 R1和R2&#xff08;R4和R5&#xff09;之间属于EBGP对等体&#xff0c;可以使用直连物理接口建立对等体关系&#xff0c;TTL值默认1。由于使用直连物理接口方式建立&#xff0c;刚好一跳到达。 [R1]bgp 100 [R1-bgp]router-i…

【教程】PLSQL查看表属性乱码解决方法

一、前言 PL/SQL是Oracle数据库的编程语言&#xff0c;用于编写存储过程、触发器、函数等。 今天用plsql想查看表的属性&#xff0c;看看各个字段的注释&#xff0c;可是打开一看&#xff0c;居然是乱码的&#xff0c;如下面这样 如果在使用PL/SQL查看表属性时出现乱码&…