欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript使用canvas實現(xiàn)錨點摳圖功能

 更新時間:2024年03月05日 15:29:09   作者:JYeontu  
在日常的圖片處理中,我們經(jīng)常會遇到需要摳圖的情況,無論是為了美化照片、制作海報,還是進行圖片合成,摳圖對于我們來說也是一種很常用的功能了,今天就讓我們一起來看下怎么使用canvas來實現(xiàn)一個錨點摳圖功能

效果展示

體驗地址

http://jyeontu.xyz/JDemo/#/imgCut

代碼實現(xiàn)

一、圖片上傳

想要進行摳圖的話我們得先有圖片是吧,所以要有個圖片上傳的功能。

1、本地圖片上傳

這里我們使用簡單的點擊按鈕上傳,前面也有文章介紹過了拖拽上傳功能的實現(xiàn),這里就不贅述了,有興趣的可以看下這篇文章:《文件拖拽上傳功能已經(jīng)爛大街了,你還不會嗎?》

這里我們直接使用input標簽來實現(xiàn)上傳功能即可:

<label for="file-upload" class="custom-file-upload">
    <i class="fas fa-cloud-upload-alt"></i> 選擇文件
</label>
<input
    v-show="false"
    id="file-upload"
    type="file"
    accept="image/*"
    @change="handleFileUpload"
/>

handleFileUpload(e) {
  let file = e.target.files[0];
  if (!file) return;
  this.srcLink = "";
  const reader = new FileReader();
  reader.onload = event => {
    const img = new Image();
    img.onload = () => {
      this.image = img;
      this.width = img.width;
      this.height = img.height;
      this.originWidth = img.width;
      this.originHeight = img.height;
      this.drawCanvas();
    };
    img.src = event.target.result;
  };
  reader.readAsDataURL(file);
}

2、在線鏈接圖片

使用Input輸入在線圖片鏈接:

<input
    type="input"
    @change="inputSrc"
    placeholder="輸入圖片在線地址"
    v-model="srcLink"
    class="input-style"
    style="width: 100%;"
/>

getImageBase64FromURL(url, callback) {
  return new Promise(resove => {
    const xhr = new XMLHttpRequest();
    xhr.onload = function() {
      const reader = new FileReader();
      reader.onloadend = function() {
        resove(reader.result);
      };
      reader.readAsDataURL(xhr.response);
    };
    xhr.open("GET", url);
    xhr.responseType = "blob";
    xhr.send();
  });
},
async inputSrc() {
  const src = await this.getImageBase64FromURL(this.srcLink);
  const img = new Image();
  img.onload = () => {
    this.image = img;
    this.width = img.width;
    this.height = img.height;
    this.drawCanvas();
  };
  img.src = src;
}

3、將上傳的圖片繪制到canvas中

drawCanvas() {
  setTimeout(() => {
    if (!this.image || !this.ctx) {
      return;
    }
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.ctx.save();
    this.ctx.translate(this.width / 2, this.height / 2);
    this.ctx.drawImage(
      this.image,
      -this.width / 2,
      -this.height / 2,
      this.width,
      this.height
    );
    this.ctx.restore();
    this.realPoints.forEach(point => {
      this.drawPoint(point.x, point.y);
    });
    this.connectPoints(); // 每次繪制canvas后連接所有點
  }, 100);
}

使用ctx.clearRect()方法清除整個畫布,以便在重新繪制之前清空之前的內容。然后,使用ctx.save()方法保存當前的繪圖狀態(tài)。

通過ctx.translate()方法將繪圖原點移動到畫布的中心位置(this.width / 2, this.height / 2),這樣可以方便地繪制圖像和點的坐標。

使用ctx.drawImage()方法繪制圖像,參數(shù)分別為圖像對象this.image、圖像左上角的x和y坐標(-this.width / 2, -this.height / 2),以及圖像的寬度和高度(this.width, this.height)。這樣就在畫布上繪制了圖像。

接著使用ctx.restore()方法恢復之前保存的繪圖狀態(tài)。

然后,通過forEach循環(huán)遍歷this.realPoints數(shù)組中的每個點,調用this.drawPoint()方法繪制每個點。

最后,調用this.connectPoints()方法連接所有的點,以繪制線條。

二、錨點選擇與撤銷

1、監(jiān)聽鼠標點擊

這里我們使用canvas來展示圖片:

<canvas
    ref="canvas"
    id="example-canvas"
    :width="width"
    :height="height"
    @click="canvasClick"
    tabindex="0"
></canvas>

監(jiān)聽canvas的點擊事件并保存點擊坐標

canvasClick(event) {
  if (!this.image || !this.ctx) {
    return;
  }
  const x = event.offsetX / (this.width / this.originWidth);
  const y = event.offsetY / (this.height / this.originHeight);
  this.points.push({ x, y }); // 將坐標添加到數(shù)組中
  const point = this.tranPoint({ x, y });
  this.drawPoint(point.x, point.y);
},

2、繪制錨點

前面我們獲取到點擊坐標了,這里我們需要在該坐標上繪制上錨點:

drawPoint(x, y) {
  // 繪制一個小圓點
  this.ctx.beginPath();
  this.ctx.arc(x, y, 4, 0, 2 * Math.PI);
  this.ctx.fillStyle = "red";
  this.ctx.fill();
  this.ctx.closePath();
  this.connectPoints(); // 每次點擊后連接所有點
},

使用beginPath()方法創(chuàng)建路徑,然后使用arc()方法繪制圓形,參數(shù)解釋如下:

  • x: 圓心的x軸坐標
  • y: 圓心的y軸坐標
  • 4: 圓的半徑
  • 0, 2 * Math.PI: 圓弧的起始角度和結束角度,這里表示繪制一個完整的圓

接下來設置fillStyle屬性為紅色,使用fill()方法填充圓形區(qū)域,并使用closePath()方法關閉路徑。

3、連接錨點

用虛線將所有錨點按順序連接起來:

connectPoints() {
  if (this.realPoints.length <= 1) {
    return;
  }
  this.ctx.beginPath();
  this.ctx.moveTo(this.realPoints[0].x, this.realPoints[0].y);
  for (let i = 1; i < this.realPoints.length; i++) {
    this.ctx.lineTo(this.realPoints[i].x, this.realPoints[i].y);
  }
  this.ctx.setLineDash([5, 5]);
  this.ctx.strokeStyle = "blue";
  this.ctx.lineWidth = 2;
  this.ctx.stroke();
  this.ctx.closePath();
}

如果realPoints數(shù)組長度大于1,接著使用beginPath()方法開始創(chuàng)建新的路徑,并通過moveTo()方法將畫筆移動到第一個點的位置(this.realPoints[0].x, this.realPoints[0].y)。隨后使用for循環(huán)遍歷realPoints數(shù)組中的每個點,使用lineTo()方法將畫筆移動到下一個點的位置(this.realPoints[i].x, this.realPoints[i].y),從而連接所有的點。

在繪制線條之前,通過setLineDash()方法設置虛線的樣式,這里是一個5像素的實線和5像素的空白,表示虛線的樣式。然后設置線條的顏色為藍色,線寬為2像素,最后通過stroke()方法繪制連接線條。最后使用closePath()方法關閉路徑。

4、錨點撤銷功能

平時我們都習慣了通過Ctrl+Z來撤銷上一步操作,這里我們也加上,通過監(jiān)聽鍵盤按鍵事件來實現(xiàn)當用戶按下Ctrl+Z組合鍵時,撤銷最后一步錨點操作,也就是將錨點列表的最后一個刪除即可:

document.addEventListener("keydown", event => {
  if (event.ctrlKey && event.key === "z") {
    event.preventDefault();
    that.undoPoint();
  }
});
undoPoint() {
  if (this.points.length > 0) {
    this.points.pop();
    this.drawCanvas();
  }
},

5、獲取錨點集合

這里我們在右邊預留了一個展示錨點列表的文本域

<textarea v-model="pointsStr" class="points-list"></textarea>
computed: {
    pointsStr() {
      return JSON.stringify(this.realPoints);
    }
}

大家覺得這里輸出錨點集合可以做什么?這里先賣個關子,下一篇博客就會需要用到這里的錨點集合了。

三、尺寸修改

頁面上我們可以對圖片尺寸進行修改,便于獲取不同比例下的錨點集:

1、頁面圖片尺寸修改

<label class="label-style">寬</label>
<input
    type="number"
    v-model="width"
    @input="resizeImage($event, 'width')"
    @keydown.ctrl.z.prevent
    class="input-style"
/>
<label class="label-style">高</label>
<input
    type="number"
    v-model="height"
    @input="resizeImage($event, 'height')"
    @keydown.ctrl.z.prevent
    class="input-style"
/>
<label class="label-style">按比例縮放</label>
<input type="checkbox" v-model="aspectRatio" class="checkbox-style" />
resizeImageByWidth(event) {
  this.width = event.target.value ? parseInt(event.target.value) : null;
  if (this.aspectRatio && this.width) {
    this.height = Math.round(
      (this.width / this.originWidth) * this.originHeight
    );
  }
},
resizeImageByHeight(event) {
  this.height = event.target.value ? parseInt(event.target.value) : null;
  if (this.aspectRatio && this.height) {
    this.width = Math.round(
      (this.height / this.originHeight) * this.originWidth
    );
  }
},
resizeImage(event, dimension) {
  if (!this.image) {
    return;
  }
  if (dimension === "width") {
    this.resizeImageByWidth(event);
  } else if (dimension === "height") {
    this.resizeImageByHeight(event);
  }
  if (
    this.aspectRatio &&
    (!event || event.target !== document.activeElement)
  ) {
    const aspectRatio = this.originWidth / this.originHeight;
    if (this.width && !this.height) {
      this.height = Math.round(this.originWidth / aspectRatio);
    } else if (!this.width && this.height) {
      this.width = Math.round(this.originHeight * aspectRatio);
    } else if (this.width / aspectRatio < this.height) {
      this.width = Math.round(this.originHeight * aspectRatio);
    } else {
      this.height = Math.round(this.originWidth / aspectRatio);
    }
  }
  this.$refs.canvas.width = this.width ? this.width : null;
  this.$refs.canvas.height = this.height ? this.height : null;
  this.image.width = this.width;
  this.image.height = this.height;
  this.drawCanvas();
}

根據(jù) dimension 的值(可能是 "width" 或 "height"),調用相應的方法來調整圖像的寬度或高度。

resizeImageByWidth(event) 方法用于根據(jù)給定的寬度調整圖像的大小。它首先將 event.target.value 轉換為整數(shù),并將結果賦值給 this.width。然后,如果啟用了縱橫比 (this.aspectRatio) 并且 this.width 有值,則計算出相應的高度,使得調整后的圖像與原始圖像保持相同的縱橫比。

resizeImageByHeight(event) 方法用于根據(jù)給定的高度調整圖像的大小。它的邏輯與 resizeImageByWidth(event) 類似,只是操作的是 this.height 和寬高比的計算方式不同。

接下來,如果啟用了縱橫比 (this.aspectRatio) 并且沒有通過鍵盤事件觸發(fā)該方法,則根據(jù)原始圖像的寬高比 (this.originWidth / this.originHeight) 進行額外的調整。具體的調整邏輯如下:

  • 如果只設置了寬度 (this.width) 而沒有設置高度 (this.height),則根據(jù)原始圖像的寬高比計算出相應的高度。
  • 如果只設置了高度 (this.height) 而沒有設置寬度 (this.width),則根據(jù)原始圖像的寬高比計算出相應的寬度。
  • 如果設置了寬度和高度,并且根據(jù)當前的寬高比計算出的寬度小于當前的高度,則根據(jù)原始圖像的寬高比計算出相應的寬度。
  • 否則,根據(jù)原始圖像的寬高比計算出相應的高度。

最后,根據(jù)調整后的寬度和高度,更新畫布(this.$refs.canvas.width 和 this.$refs.canvas.height),以及圖像的寬度和高度 (this.image.width 和 this.image.height)。然后調用 drawCanvas() 方法重新繪制畫布。

2、錨點根據(jù)縮放比例進行修改

圖片縮放之后,錨點位置也要進行對應的縮放。

tranPoint(point) {
  let { x, y } = point;
  x = x * (this.width / this.originWidth);
  y = y * (this.height / this.originHeight);
  return { x, y };
}

四、摳圖預覽

1、圖片預覽組件

這里我們簡單編寫一個圖片預覽彈窗組件:

<template>
  <div>
    <div class="preview-overlay" @click="hidePreview">
      <img :src="currentImage" alt="preview image" class="preview-image" />
      <div class="export-button" @click.stop="handleExport">
        <span>導出圖片</span>
        <span class="shine"></span>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "previewImg",
  props: {
    imageList: {
      type: Array,
      default: () => []
    },
    currentImage: {
      type: String,
      default: ""
    }
  },
  data() {
    return {};
  },
  methods: {
    hidePreview() {
      this.$emit("close");
    },
    handleExport() {
      this.$emit("export", this.currentImage);
    }
  }
};
</script>
<style>
.preview-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 999;
}
.preview-image {
  max-width: 80%;
  max-height: 80%;
  object-fit: contain;
}
.export-button {
  position: absolute;
  bottom: 20px;
  padding: 10px;
  background-color: #00aaff;
  color: white;
  border-radius: 5px;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 16px;
  font-weight: bold;
  text-align: center;
  box-shadow: 0 0 10px #00aaff;
  overflow: hidden;
}
.export-button:hover {
  background-color: #00e5ff;
}
.shine {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: linear-gradient(
    45deg,
    #ffffff 10%,
    rgba(255, 255, 255, 0) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: exportButtonShine 2s linear infinite;
}
@keyframes exportButtonShine {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>

模板部分包含了一個遮罩層和圖片預覽,以及一個導出按鈕。當用戶點擊遮罩層時,會觸發(fā) hidePreview 方法,關閉預覽。圖片預覽部分使用了動態(tài)綁定的 :src 屬性來顯示當前的圖片,而導出按鈕則綁定了 handleExport 方法,在點擊時會觸發(fā)導出操作。

腳本部分定義了名為 "previewImg" 的組件,其中包括了兩個屬性 imageList 和 currentImage,分別用于接收圖片列表和當前顯示的圖片。在方法部分,定義了 hidePreview 方法用于關閉預覽,并通過 $emit 向父組件發(fā)送 "close" 事件,以通知父組件關閉預覽。另外還有 handleExport 方法,用于處理導出操作,并通過 $emit 向父組件發(fā)送 "export" 事件,并傳遞當前圖片的路徑。

2、摳圖操作

cutImg() {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  if (!this.image || !ctx) {
    return;
  }
  const image = this.image;
  canvas.width = image.width;
  canvas.height = image.height;

  // 定義剪切路徑
  const cutPath = this.realPoints;
  ctx.beginPath();
  ctx.moveTo(cutPath[0].x, cutPath[0].y);
  for (let i = 1; i < cutPath.length; i++) {
    ctx.lineTo(cutPath[i].x, cutPath[i].y);
  }
  ctx.closePath();
  ctx.clip();

  // 繪制圖片
  ctx.drawImage(image, 0, 0, this.width, this.height);
  // 將Canvas元素轉換為PNG圖像
  const imgData = canvas.toDataURL("image/png");
  this.currentImage = imgData;
  this.showImg = true;
}

獲取要剪切的圖片對象,并根據(jù)該圖片的寬度和高度設置 <canvas> 的寬度和高度。

然后,定義剪切路徑,通過遍歷 cutPath 數(shù)組中的點坐標,使用 ctx.lineTo() 方法繪制路徑。最后使用 ctx.closePath() 方法閉合路徑,并調用 ctx.clip() 方法將剪切路徑應用于上下文。

接著,使用 ctx.drawImage() 方法繪制剪切后的圖片。傳入的參數(shù)包括原始圖片對象、剪切后的起始點坐標以及剪切后的寬度和高度。

最后,使用 canvas.toDataURL() 方法將 <canvas> 元素轉換為 base64 編碼的 PNG 圖像數(shù)據(jù),并將該數(shù)據(jù)賦值給 imgData 變量。然后將 imgData 賦值給 currentImage 屬性,將剪切后的圖片顯示出來(通過在模板中綁定 currentImage)。

五、導出摳圖圖片

downloadImg(imgData) {
  // 創(chuàng)建一個鏈接元素,將圖像數(shù)據(jù)作為URL設置給它
  const link = document.createElement("a");
  link.download = "myImage.png";
  link.href = imgData;

  // 觸發(fā)鏈接的下載事件
  link.click();
}

首先,通過 document.createElement("a") 創(chuàng)建一個 <a> 元素,并將該元素賦值給 link 變量。

然后,將要下載的圖片的文件名設置為 "myImage.png",可以根據(jù)實際需要修改。

接下來,將圖片數(shù)據(jù) imgData 設置為鏈接元素的 href 屬性,這樣點擊鏈接時會下載該圖片。

最后,通過調用 link.click() 方法觸發(fā)鏈接的點擊事件,從而觸發(fā)下載操作。

以上就是JavaScript使用canvas實現(xiàn)錨點摳圖功能的詳細內容,更多關于JavaScript canvas錨點摳圖的資料請關注腳本之家其它相關文章!

相關文章

最新評論