【Canvas】图片的拖拽和下载

效果预览:

动画.gif

👉 项目地址

实现[Vue3+TypeScript]

1.创建画布并初始化

<canvas id="canvas" width="500" height="500"></canvas>

初始化画布:

const canvas = ref<HTMLCanvasElement>();
let ctx: CanvasRenderingContext2D;

const width = 500; // 画布的固定宽度
const height = 500; // 画布的固定高度

canvas.value = document.getElementById("canvas") as HTMLCanvasElement;
ctx = canvas.value!.getContext("2d") as CanvasRenderingContext2D;

2.导入猫猫头图片,存储为数组

const imgs = [
  { name: "p1", url: "/img/cat1.png" },
  { name: "p2", url: "/img/cat2.png" },
  { name: "p3", url: "/img/cat3.png" },
];

创建新数组imagesData用来存储猫猫头数据和图片在画布中的位置信息:

const imagesData: {
  img: HTMLImageElement; // 图片信息
  name: string; // 图片名称
  x: number; // x方向位置
  y: number; // y方向位置
  w: number; // 绘制宽度
  h: number; // 绘制高度
}[] = [];

3.将猫猫头随机绘制在画布上

imgs.forEach(item => {
  let img = new Image();
  img.src = item.url;
  const name = item.name;
  img.onload = function () {
  // 这里取固定宽度100
  const w = 100;
  const h = (w / img.width) * img.height;
  const x = Math.random() * (width - w);
  const y = Math.random() * (height - h);
  const obj = { img, name, x, y, w, h };
  imagesData.push(obj);
  drawByObj(obj);
  };
});

function drawByObj(obj) {
  ctx.drawImage(obj.img, obj.x, obj.y, obj.w, obj.h);
}

4.猫猫头之点击

首先要进行判断,在鼠标点下去时的目标是否为我们绘制的图片,这里可以使用isPointInPath方法。

  • isPointInPath:
    是 Canvas 2D API 用于检测某点是否在路径的描边线上的方法。返回值是一个布尔值,当这个点在路径的描边线上,则返回true,否则返回false。

  • 使用方法:context.isPointInPath(x,y)。其中x,y分别是给定点的横、纵坐标。

  • 注意:该方法是针对路径的,比如canvas中的rect、arc方法都可以用,但是fillRect不可以用,因为它不是路径;而且仅对当前的路径有效,而且如果当前路径有多个子路径,则只对第一个子路径有效。

给画布添加鼠标按下事件mousedown,记录当前点击的位置,对该位置进行判断,若该位置是猫猫头,记录下这张图片的在imagesData中的位置target

let clickCoordinate = { x: 0, y: 0 };
let target;

canvas.value.addEventListener("mousedown", mousedownFn, false);

function mousedownFn(e: MouseEvent) {
  clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
  clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
  console.log("初次点击位置:" + clickCoordinate.x + "--" + clickCoordinate.y);
  checkElement();
  if (target == undefined) return;
  canvas.value!.addEventListener("mousemove", mousemoveFn, false);
  canvas.value!.addEventListener("mouseup", mouseupFn, false);
}

/** 判断点击的是哪个 */
function checkElement() {
  imagesData.forEach((item, index) => {
  // 绘制辅助路径
  drawGuideByObj(item);
  if (ctx.isPointInPath(clickCoordinate.x, clickCoordinate.y)) {
    console.log("点击的元素是:", item.name);
    target = index;
  }
 });
}
// 根据猫猫头的宽度和高度绘制辅助路径,获取正确的ctx,才能进行判断
function drawGuideByObj(obj: any) {
  ctx.beginPath();
  ctx.lineWidth = 4;
  ctx.strokeStyle = "green";
  ctx.rect(obj.x, obj.y, obj.w, obj.h);
  ctx.stroke();
}

5.猫猫头之拖拽

结果判断后,如果target的值存在,那么可以对猫猫头进行拖拽,这时需要给画布添加鼠标移动事件mousemove和鼠标抬起mouseup事件。

function mousedownFn(e: MouseEvent) {
  clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
  clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
  console.log("初次点击位置:" + clickCoordinate.x + "--" + clickCoordinate.y);
  checkElement();
  if (target == undefined) return;
  canvas.value!.addEventListener("mousemove", mousemoveFn, false);
  canvas.value!.addEventListener("mouseup", mouseupFn, false);
}

mousemove触发事件:根据MouseEvent的值,更新猫猫头的最新位置,并清空画布重新绘制:

function mousemoveFn(e: MouseEvent) {
  // 计算点击位置和图片原点位置的差
  let sx = clickCoordinate.x - imagesData[target].x;
  let sy = clickCoordinate.y - imagesData[target].y;
  // 计算移动元素的坐标
  imagesData[target].x = e.pageX - canvas.value!.offsetLeft - sx;
  imagesData[target].y = e.pageY - canvas.value!.offsetTop - sy;
  // 重新赋值点击位置
  clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
  clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
  // 清空画布
  ctx.clearRect(0, 0, width, height);
  // 清空画布以后重新绘制
  imagesData.forEach(i => drawByObj(i));
}

mouseup触发事件:鼠标抬起后,移除画布相关事件:

function mouseupFn(e: MouseEvent) {
  console.log(e.pageX + "----" + e.pageY);
  // 鼠标抬起以后移除事件
  canvas.value!.removeEventListener("mousemove", mousemoveFn, false);
  canvas.value!.removeEventListener("mouseup", mouseupFn, false);
  target = undefined;
}

6.猫猫头之下载

新增下载触发事件:

<div class="btn" @click="save">保存</div>

将canvas存储为本地图片有3步:

  • 将canvas转换为base64的url:toDataURL
  • 将base64转换为文件对象
  • 通过虚拟点击下载文件

代码如下:

const save = () => {
  // 将canvas转换成base64的url
  let url = canvas.value!.toDataURL("image/png");
  imgOnPage.value!.src = url;
  // 将base64转换为文件对象
  let arr = url.split(",");
  let mime = arr[0].match(/:(.*?);/)![1];
  let binaryString = window.atob(arr[1]);
  let binaryLen = binaryString.length;
  let bytes = new Uint8Array(binaryLen);
  for (let i = 0; i < bytes.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
  }
  let file = new File([bytes], "filename", { type: mime });
  // 虚拟点击
  const link = document.createElement("a");
  link.download = file.name;
  let href = URL.createObjectURL(file);
  link.href = href;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(href);
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容