Canvas拖动图片效果

效果预览

请添加图片描述

代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>mouse event</title>
  </head>
  <body>
    <div>
      <canvas
        id="cs"
        width="800"
        height="400"
        style="border: 2px solid green"
      ></canvas>
      <button onclick="exportImg()">导出图片</button>
    </div>

    <script type="text/javascript">
      let canvas = document.getElementById("cs");
      // 获取canvas的宽高
      let width = canvas.width;
      let height = canvas.height;

      let ctx = canvas.getContext("2d");
      let now = { x: 0, y: 0 }; // 鼠标的位置
      let pos = { x1: 0, y1: 0, x2: 0, y2: 0, x3: 0, y3: 0, x4: 0, y4: 0 }; // 图片起始点的位置,这里默认(x1, y1)为(0, 0)
      // 鼠标按下、鼠标移动和鼠标松开是两个分散的动作,现在要取它两的交集且有顺序,
      // 即:鼠标按下、鼠标移动和鼠标松开,将其认为是一个动作。

      let isDown = false;

      // 创建img元素
      let img = document.createElement("img");
      // 设置它的src,这样图片会被加载
      img.src = "./a.jpeg";
      img.style.position = "absolute";
      // 图片的宽高
      img.width = "100";
      img.height = "100";
      // 当图片加载完成之后,将其绘制到canvas上
      // 如果没有这句话,图片绘制可能不会发生,因为图片未加载时为空。
      img.onload = () => {
        drawCanvas(img, "#FFFFFF");
        // 更新图片的起始点
        updatePos(pos.x1, pos.y1);
      };

      // 添加鼠标事情,来实现图片的拖放功能。
      // 当鼠标按下时,获取当前的坐标
      canvas.onmousedown = (e) => {
        isDown = true; // 鼠标按下时,设置isDown为true,此时移动鼠标才认为是有效。
        console.log("鼠标按下");
        let x = e.pageX - canvas.offsetLeft; // 后面这个是偏移量,但是在这里为0
        let y = e.pageY - canvas.offsetTop;
        now.x = x;
        now.y = y;
        console.log(x + " -> " + y);

        if (!ctx.isPointInPath(x, y)) {
          console.log("鼠标没在路径内");
          return;
        }

        drawCanvas(img, "red");
      };

      // 在鼠标移动时,不断重绘制整个canvas
      canvas.onmousemove = (e) => {
        if (!isDown) {
          // 鼠标未按下则直接返回,不去响应该事件。
          return;
        }

        // 获取点的坐标可以封装成函数
        let x = e.pageX;
        let y = e.pageY;

        console.log(x + " " + y);
        if (!ctx.isPointInPath(x, y)) {
          console.log("鼠标没在路径内");
          return;
        }
        // 这里需要限制鼠标不能越过界限的问题,图片移动到界面外的效果不好

        // 清空当前的canvas图形
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 计算图片的绘制起点的变化位置
        pos.x1 = pos.x1 + (x - now.x);
        pos.y1 = pos.y1 + (y - now.y);
        // 更新其它点的位置
        updatePos(pos.x1, pos.y1);
        // 判断四个点的位置情况,不能越界
        judgePosition();

        // 重绘制偏移之后的canvas
        ctx.drawImage(img, pos.x1, pos.y1, img.width, img.height);

        ctx.beginPath();
        ctx.strokeStyle = "red";
        ctx.rect(pos.x1, pos.y1, img.width, img.height);
        ctx.stroke();

        now.x = x;
        now.y = y;
        console.log("鼠标在移动..." + x + " --> " + y);
      };

      canvas.onmouseup = (e) => {
        isDown = false; // 鼠标松开,则上述封装的动作结束。
        console.log("鼠标松开");
        drawCanvas(img, "#FFFFFF");
      };

      // 如果鼠标按下然后移动的过程中离开了当前元素,再松开,但是无法触发鼠标松开事件了,
      // 所以当监听到鼠标移出元素时,必须也要将isDown设置成false。
      canvas.onmouseout = (e) => {
        isDown = false;
        console.log("鼠标离开了画布元素");
        drawCanvas(img, "#FFFFFF");
        // 判断四个点的位置情况,不能越界
        judgePosition();
        // 重绘制偏移之后的canvas
        ctx.drawImage(img, pos.x1, pos.y1, img.width, img.height);
      };

      function drawCanvas(img, color) {
        ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空当前的canvas图形
        ctx.drawImage(img, pos.x1, pos.y1, img.width, img.height); // 这里不应该使用全局变量传参数的。
        ctx.beginPath();
        ctx.strokeStyle = color;
        ctx.rect(pos.x1, pos.y1, img.width, img.height);
        ctx.stroke();
      }

      // 因为图片是矩形,所以只要知道一点就可以确定其余四点了,
      // 这里我们取 (x1,y1),即左上角的点,这样比较方便。
      // 因为x1,y1也在改变,所以需要先更新x1,y1.
      function updatePos(x, y) {
        console.log("传入的参数值:", x, y);
        pos.x1 = x;
        pos.y1 = y;
        pos.x2 = x + img.width;
        pos.y2 = y;
        pos.x3 = x + img.width;
        pos.y3 = y + img.height;
        pos.x4 = x;
        pos.y4 = y + img.height;
      }

      // 判断位置,当点越界时,进行处理
      function judgePosition() {
        // 先看一下点的规则
        console.log("judgePosition:", pos);

        // 单边出界的情况和两边出界共三种情况
        // 只要保证左上角和右下角的三种情况都不出界,所有情况都不会出界了。
        if (pos.x1 < 0 && pos.y1 > 0) {
          updatePos(0, pos.y1);
        } else if (pos.x1 > 0 && pos.y1 < 0) {
          updatePos(pos.x1, 0);
        } else if (pos.x1 < 0 && pos.y1 < 0) {
          updatePos(0, 0);
        } else if (pos.x3 > width && pos.y3 < height) {
          updatePos(width - img.width, pos.y3 - img.height);
        } else if (pos.x3 < width && pos.y3 > height) {
          updatePos(pos.x3 - img.width, height - img.height);
        } else if (pos.x3 > width && pos.y3 > height) {
          updatePos(width - img.width, height - img.height);
        }
      }

      // 导出canvas为图片
      function exportImg() {
        var dataURL = canvas.toDataURL("image/png");
        downloadImage(dataURL, "canvas-image.png");
      }

      function downloadImage(dataURL, filename) {
        // 创建一个a元素,用于触发下载
        var link = document.createElement("a");
        link.download = filename;
        link.href = dataURL;
        link.click();
      }
    </script>
  </body>
</html>

参考链接

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

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

相关文章

doss攻击为什么是无解的?

这个让Google、亚马逊等实力巨头公司也无法避免的攻击。可以这么说&#xff0c;是目前最强大、最难防御的攻击之一&#xff0c;属于世界级难题&#xff0c;并且没有解决办法。 Doss攻击的原理不复杂&#xff0c;就是利用大量肉鸡仿照真实用户行为&#xff0c;使目标服务器资源…

CSS导读 (复合选择器 下)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 2.5 伪类选择器 2.6 链接伪类选择器 2.6.1 链接伪类注意事项 2.6.2 链接伪类选择器实际开发中的写法 2.7 …

每日一题 第八十九期 洛谷 [NOIP2017 提高组] 奶酪

[NOIP2017 提高组] 奶酪 题目背景 NOIP2017 提高组 D2T1 题目描述 现有一块大奶酪&#xff0c;它的高度为 h h h&#xff0c;它的长度和宽度我们可以认为是无限大的&#xff0c;奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系&#xff0c;在坐标系…

Redis-缓存击穿-逻辑过期

Redis-缓存击穿-逻辑过期实现 缓存击穿&#xff1a;也称热点key问题&#xff0c;大量访问一个key&#xff0c;而这个key恰巧到期了&#xff0c;导致大量的请求访问数据库。增大数据库的负担。为了解决这个问题可以采用互斥锁或逻辑过期的方式解决。本章采用逻辑过期的方式解决…

Golang笔记(下)

Golang学习笔记&#xff08;下&#xff09; 前篇&#xff1a;Golang学习笔记(上) 十四、错误处理 14.1使用error类型 func New(text string) error例子&#xff1a; package mainimport ("errors" // 导入errors包"fmt" )func main() {var number, divi…

【数据结构】树与二叉树遍历算法的应用(求叶子节点个数、求树高、复制二叉树、创建二叉树、二叉树存放表达式、交换二叉树每个结点的左右孩子)

目录 求叶子节点个数、求树高、复制二叉树、创建二叉树、二叉树存放表达式、交换二叉树每个结点的左右孩子应用一&#xff1a;统计二叉树中叶子结点个数的算法写法一&#xff1a;使用静态变量写法二&#xff1a;传入 count 作为参数写法三&#xff1a;不使用额外变量 应用二&am…

【Linux】socket编程2

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 目录 &#x1f449;&#x1f3fb;客户端代码Makefile(生成目标文件)UdpClient.cc(客户端代码)服务端代码部分优化1&#xff08;接受客户端时显示客…

基于51单片机低中高音7键电子琴音乐播放器

基于51单片机电子琴音乐播放器 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.可以使用按键切换音乐播放模式和弹奏模式&#xff1b; 2.LED灯显示在使用哪种模式&#xff1b; 3.音乐…

Redis部署之主从

使用两台云服务器&#xff0c;在 Docker 下部署。 Redis版本为&#xff1a;7.2.4 下载并配置redis 配置文件 下载 wget -c http://download.redis.io/redis-stable/redis.conf配置 master节点配置 bind 0.0.0.0 # 使得Redis服务器可以跨网络访问,生产环境请考虑…

第十四届蓝桥杯C/C++大学B组题解(二)

6、岛屿个数 #include <bits/stdc.h> using namespace std; const int M51; int T,m,n; int vis[M][M],used[M][M]; int dx[]{1,-1,0,0,1,1,-1,-1}; int dy[]{0,0,1,-1,1,-1,1,-1}; string mp[M]; struct node{//记录一点坐标 int x,y; }; void bfs_col(int x,int y){ qu…

C++ | Leetcode C++题解之第14题最长公共前缀

题目&#xff1a; 题解&#xff1a; class Solution { public:string longestCommonPrefix(vector<string>& strs) {if (!strs.size()) {return "";}int minLength min_element(strs.begin(), strs.end(), [](const string& s, const string& t)…

同步压缩理论

参考 在频率方向进行能量重新分配&#xff08;分配到中心&#xff09; 时频重排

实验4 DHCP基础配置

实验4 DHCP基础配置 一、 原理描述二、 实验目的三、 实验内容四、 实验配置五、 实验步骤1.基本配置2.配置DHCPServer功能3.配置DHCP Client 一、 原理描述 动态主机配置协议 DHCP是一个局域网的网络协议&#xff0c;使用UDP协议工作&#xff0c;主要有两个用途&#xff1a;用…

大话设计模式——16.命令模式(Command Pattern)

简介 请求以命令的形式包裹在对象中&#xff0c;并传给调用对象。调用对象寻找可以处理该命令的对象进行执行。命令模式是一种特殊的策略模式&#xff0c;体现多个策略执行的问题&#xff0c;而不是选择的问题 UML图 应用场景 界面选择、键盘、按钮、事件操作都类似命令模式 …

前端工程化理解 (2024 面试题)

最好介绍远古世界最好随性一点&#xff0c;不要太刻板 &#xff0c;不然像背书 什么是前端工程化&#xff1f; - 知乎 前端工程化的历史 互联网初期&#xff0c;09 年以前&#xff0c;页面只需要展示一些列表、表格、文章内容以及简单图片即可&#xff0c;其目的是为了传送信…

身份证正面打印、反面打印与打印在同一页

1 打印身份证正面 将身份证正面朝下放置&#xff0c;按打印机键盘上的数字【3】&#xff0c;再按【Start】键&#xff0c;选择A4大小打印&#xff0c;如图(1)所示&#xff1a; 图(1) A4纸复印 2 打印身份证反面 将身份证反面朝下放置&#xff0c;按打印机键盘上的数字【3】&…

KVM 高级功能部署

目录 一、案例分析 1.1、案例概述 1.2、案例前置知识点 1&#xff09;KVM 虚拟机迁移 2&#xff09;KSM 内核同页合并 1.3、案例环境 1&#xff09;本案例环境 2&#xff09;案例需求 3&#xff09;案例实现思路 二、案例实施 2.1、静态迁移 1&#xff09;在…

springboot+vue药店药品进销存采购管理系统0z10z

本系统采用intellij idea支持eclipse 项目架构&#xff1a;B/S架构web 开发语言&#xff1a;java 前端技术&#xff1a;vue.jsElementUi 后端框架&#xff1a;django、mybatis、Springmvc 运行环境&#xff1a;win10/win11、jdk1.8 可行性论证 社会可行性 开发本系统&#xff…

XC6206稳压芯片

mark 662k XC6206 的基本特性。这是一个 SOT23封装的 3.3V 稳压器。它输出最大工作电流为 100mA 最大特点便宜 参考链接 XC6206稳压芯片 (qq.com)https://mp.weixin.qq.com/s?__bizMzA5NjQyNjc2NQ&mid2452268489&idx1&sn90e920c596e3c2a382f81929c6313977&c…

Linux--进程的概念(一)

目录 一、冯诺依曼体系结构二、操作系统2.1 什么是操作系统2.2 操作系统的意义 三、进程3.1 进程的基本概念3.2 描述进程——PCB3.3 进程和程序的区别3.4 task_struct-PCB的一种3.5 task_struct的内容分类 四、如何查看进程4.1 通过系统文件查看进程4.2 通过ps指令查看进程 五、…