【Canvas】圖片的拖拽和下載

效果預(yù)覽:

動(dòng)畫.gif

?? 項(xiàng)目地址?

實(shí)現(xiàn)[Vue3+TypeScript]

1.創(chuàng)建畫布并初始化

<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.導(dǎo)入貓貓頭圖片愉豺,存儲(chǔ)為數(shù)組

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

創(chuàng)建新數(shù)組imagesData用來(lái)存儲(chǔ)貓貓頭數(shù)據(jù)和圖片在畫布中的位置信息:

const imagesData: {
  img: HTMLImageElement; // 圖片信息
  name: string; // 圖片名稱
  x: number; // x方向位置
  y: number; // y方向位置
  w: number; // 繪制寬度
  h: number; // 繪制高度
}[] = [];

3.將貓貓頭隨機(jī)繪制在畫布上

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.貓貓頭之點(diǎn)擊

首先要進(jìn)行判斷协饲,在鼠標(biāo)點(diǎn)下去時(shí)的目標(biāo)是否為我們繪制的圖片,這里可以使用isPointInPath方法圃酵。

  • isPointInPath:
    是 Canvas 2D API 用于檢測(cè)某點(diǎn)是否在路徑的描邊線上的方法悦施。返回值是一個(gè)布爾值虐呻,當(dāng)這個(gè)點(diǎn)在路徑的描邊線上,則返回true琼腔,否則返回false瑰枫。

  • 使用方法:context.isPointInPath(x,y)。其中x,y分別是給定點(diǎn)的橫展姐、縱坐標(biāo)躁垛。

  • 注意:該方法是針對(duì)路徑的,比如canvas中的rect圾笨、arc方法都可以用教馆,但是fillRect不可以用,因?yàn)樗皇锹窂嚼薮铮欢覂H對(duì)當(dāng)前的路徑有效土铺,而且如果當(dāng)前路徑有多個(gè)子路徑,則只對(duì)第一個(gè)子路徑有效板鬓。

給畫布添加鼠標(biāo)按下事件mousedown悲敷,記錄當(dāng)前點(diǎn)擊的位置,對(duì)該位置進(jìn)行判斷俭令,若該位置是貓貓頭后德,記錄下這張圖片的在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("初次點(diǎn)擊位置:" + clickCoordinate.x + "--" + clickCoordinate.y);
  checkElement();
  if (target == undefined) return;
  canvas.value!.addEventListener("mousemove", mousemoveFn, false);
  canvas.value!.addEventListener("mouseup", mouseupFn, false);
}

/** 判斷點(diǎn)擊的是哪個(gè) */
function checkElement() {
  imagesData.forEach((item, index) => {
  // 繪制輔助路徑
  drawGuideByObj(item);
  if (ctx.isPointInPath(clickCoordinate.x, clickCoordinate.y)) {
    console.log("點(diǎn)擊的元素是:", item.name);
    target = index;
  }
 });
}
// 根據(jù)貓貓頭的寬度和高度繪制輔助路徑,獲取正確的ctx抄腔,才能進(jìn)行判斷
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.貓貓頭之拖拽

結(jié)果判斷后瓢湃,如果target的值存在理张,那么可以對(duì)貓貓頭進(jìn)行拖拽,這時(shí)需要給畫布添加鼠標(biāo)移動(dòng)事件mousemove和鼠標(biāo)抬起mouseup事件绵患。

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

mousemove觸發(fā)事件:根據(jù)MouseEvent的值雾叭,更新貓貓頭的最新位置,并清空畫布重新繪制:

function mousemoveFn(e: MouseEvent) {
  // 計(jì)算點(diǎn)擊位置和圖片原點(diǎn)位置的差
  let sx = clickCoordinate.x - imagesData[target].x;
  let sy = clickCoordinate.y - imagesData[target].y;
  // 計(jì)算移動(dòng)元素的坐標(biāo)
  imagesData[target].x = e.pageX - canvas.value!.offsetLeft - sx;
  imagesData[target].y = e.pageY - canvas.value!.offsetTop - sy;
  // 重新賦值點(diǎn)擊位置
  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觸發(fā)事件:鼠標(biāo)抬起后落蝙,移除畫布相關(guān)事件:

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

6.貓貓頭之下載

新增下載觸發(fā)事件:

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

將canvas存儲(chǔ)為本地圖片有3步:

  • 將canvas轉(zhuǎn)換為base64的url:toDataURL
  • 將base64轉(zhuǎn)換為文件對(duì)象
  • 通過(guò)虛擬點(diǎn)擊下載文件

代碼如下:

const save = () => {
  // 將canvas轉(zhuǎn)換成base64的url
  let url = canvas.value!.toDataURL("image/png");
  imgOnPage.value!.src = url;
  // 將base64轉(zhuǎn)換為文件對(duì)象
  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 });
  // 虛擬點(diǎn)擊
  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);
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末织狐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子筏勒,更是在濱河造成了極大的恐慌移迫,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏寨,死亡現(xiàn)場(chǎng)離奇詭異起意,居然都是意外死亡鹰服,警方通過(guò)查閱死者的電腦和手機(jī)病瞳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)悲酷,“玉大人套菜,你說(shuō)我怎么就攤上這事∩枰祝” “怎么了逗柴?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)顿肺。 經(jīng)常有香客問我戏溺,道長(zhǎng),這世上最難降的妖魔是什么屠尊? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任旷祸,我火速辦了婚禮,結(jié)果婚禮上讼昆,老公的妹妹穿的比我還像新娘托享。我一直安慰自己,他們只是感情好浸赫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布闰围。 她就那樣靜靜地躺著,像睡著了一般既峡。 火紅的嫁衣襯著肌膚如雪羡榴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天运敢,我揣著相機(jī)與錄音校仑,去河邊找鬼么夫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肤视,可吹牛的內(nèi)容都是我干的档痪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邢滑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腐螟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起困后,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乐纸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后摇予,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汽绢,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年侧戴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宁昭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酗宋,死狀恐怖积仗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜕猫,我是刑警寧澤寂曹,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站回右,受9級(jí)特大地震影響隆圆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翔烁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一渺氧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧租漂,春花似錦阶女、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至业筏,卻和暖如春憔杨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒜胖。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工消别, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抛蚤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓寻狂,卻偏偏與公主長(zhǎng)得像岁经,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛇券,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容