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

前端Vue3圖像編輯功能實現(xiàn)代碼(并生成mask圖)

 更新時間:2025年09月16日 09:22:30   作者:懮?俍  
隨著互聯(lián)網(wǎng)的飛速發(fā)展,圖片編輯已經(jīng)成為人們日常生活中經(jīng)常使用的一項技能,下面這篇文章主要介紹了前端Vue3圖像編輯功能實現(xiàn)的相關資料,并生成mask圖,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

效果展示

存在一個需求同豆包的圖像生成的區(qū)域重繪功能,類似與下面這種

需求詳細拆解

1. 鼠標交互視覺反饋

  • 懸浮狀態(tài):當鼠標移入圖像區(qū)域時,顯示一個跟隨鼠標移動的空心圓形光標

  • 消失條件:在以下情況下空心圓消失:

    • 鼠標移出圖像區(qū)域

    • 鼠標點擊操作

    • 鼠標按下并移動時

  • 重現(xiàn)條件:鼠標松開后,空心圓重新出現(xiàn)

2. 涂鴉繪制功能

  • 按下狀態(tài)

    • 顯示與空心圓大小相同的實心半透明圓形

    • 開始記錄繪制軌跡

  • 拖動狀態(tài)

    • 根據(jù)鼠標移動路徑形成連續(xù)軌跡

    • 軌跡末端保持圓形形狀

    • 實現(xiàn)類似自由涂鴉筆的效果

  • 結束條件:鼠標松開后結束當前涂鴉記錄

3. 操作確認功能

  • 發(fā)送框顯示:鼠標松開后,在鼠標當前位置顯示操作確認框

  • 發(fā)送框隱藏:點擊任意位置后隱藏發(fā)送框

4. 歷史記錄管理

  • 記錄單元:每次鼠標松開視為一次完整的涂鴉操作記錄

  • 撤銷/重做

    • 支持逐步撤銷之前的涂鴉操作

    • 支持重做已撤銷的操作

  • 清空功能:一鍵清除所有涂鴉痕跡

5. 尺寸調節(jié)功能

  • 提供滑塊控件用于調整:

    • 涂鴉筆觸的直徑大小

    • 空心光標的直徑大小

6. 數(shù)據(jù)導出功能

  • 生成與圖像實際尺寸一致的Base64格式mask圖

  • mask圖應準確反映所有涂鴉痕跡

技術實現(xiàn)方案

畫布分層設計

采用多層canvas架構實現(xiàn)功能分離和性能優(yōu)化:

  1. 基礎圖像層

    • 負責顯示原始圖像

    • 作為其他層的背景參考

  2. 涂鴉繪制層

    • 實時記錄和顯示用戶涂鴉軌跡

    • 保存所有涂鴉操作數(shù)據(jù)

  3. 光標反饋層

    • 顯示跟隨鼠標的空心圓光標

    • 處理所有光標狀態(tài)變化

  4. 臨時處理層

    • 用于生成最終的mask圖像

    • 確保輸出尺寸與實際圖像一致

    • 不參與界面顯示,僅用于數(shù)據(jù)處理

實現(xiàn)流程邏輯

  1. 初始化階段

    • 加載圖像到基礎層

    • 設置各canvas層級關系

    • 初始化涂鴉參數(shù)(默認大小、顏色等)

  2. 交互階段

    • 監(jiān)聽鼠標事件(移入、移出、按下、移動、松開)

    • 根據(jù)狀態(tài)切換顯示對應canvas層內容

    • 實時更新涂鴉軌跡

  3. 數(shù)據(jù)處理階段

    • 維護操作歷史棧(支持撤銷/重做)

    • 在臨時canvas生成mask圖像

    • 轉換為Base64格式輸出

  4. UI控制

    • 綁定滑塊事件調整大小參數(shù)

    • 實現(xiàn)清空按鈕功能

    • 管理發(fā)送框的顯示/隱藏

性能優(yōu)化考慮

  • 采用分層渲染避免不必要的重繪

  • 使用requestAnimationFrame優(yōu)化光標跟隨

  • 對歷史記錄采用增量存儲方式

  • 僅在需要時生成mask圖像

這種實現(xiàn)方式既能滿足所有功能需求,又能保證良好的用戶體驗和性能表現(xiàn)。

相關代碼如下:

<template>
  <div class="img-edit-box">
    <div class="img-edit-box-top" v-if="currentImgEdit == 'all'">
      <div class="img-edit-btn-box" @click="quoteImgEditChange">
        <!-- @click="
          quoteChange(true, currentImgUrl, 'imageEdit', currentImgQuestion)
        " -->
        <div class="img-edit-btn-zhineng"></div>
        <div class="img-edit-btn-text">智能編輯</div>
      </div>
      <div class="img-edit-btn-box" @click="changeEditStatus('scope')">
        <div class="img-edit-btn-chonghui"></div>
        <div class="img-edit-btn-text">區(qū)域重繪</div>
      </div>
      <!-- <div class="img-edit-btn-box">
            <div class="img-edit-btn-kuotu"></div>
            <div class="img-edit-btn-text">擴圖</div>
          </div> -->
      <!-- <div class="img-edit-btn-box">
            <div class="img-edit-btn-cachu"></div>
            <div class="img-edit-btn-text">擦除</div>
          </div> -->
      <div class="img-edit-btn-right to-right">
        <div
          class="img-edit-btn-box"
          @click="downloadBase64"
        >
          <div class="img-edit-btn-download"></div>
          <div class="img-edit-btn-text">下載原圖</div>
        </div>
        <div class="divide-line"></div>
        <div class="img-edit-btn-box close-box" @click="closeImgEditVisible">
          <div class="close-icon"></div>
        </div>
      </div>
    </div>
    <div v-if="currentImgEdit == 'scope'" class="img-edit-box-top flex-center">
      <div class="img-edit-btn-left">
        <div
          class="img-edit-btn-box close-box"
          @click="changeEditStatus('all')"
        >
          <div class="back-icon"></div>
        </div>
      </div>
      <div class="img-edit-btn-center">
        <!-- <div class="img-edit-btn-box">
              <div class="img-edit-btn-download"></div>
            </div> -->
        <div class="img-edit-btn-slider">
          <el-slider
            v-model="circleDiameter"
            :min="30"
            :max="100"
            input-size="mini"
            @mousedown="clickCircleDiameter"
            @change="changeCircleDiameter"
            @input="inputCircleDiameter"
          ></el-slider>
        </div>
        <div class="divide-line"></div>
        <div
          class="close-box"
          :class="[step == 0 ? 'img-edit-btn-box-none' : 'img-edit-btn-box']"
          @click="undo"
        >
          <div class="chexiao-icon"></div>
        </div>
        <div
          class="close-box"
          :class="[
            step == history.length - 1
              ? 'img-edit-btn-box-none'
              : 'img-edit-btn-box',
          ]"
          @click="redo"
        >
          <div class="huanyuan-icon"></div>
        </div>
        <div class="divide-line"></div>
        <div
          :class="[step == 0 ? 'img-edit-btn-box-none' : 'img-edit-btn-box']"
          style="width: max-content"
          @click="clearCanvas"
        >
          清除
        </div>
        <!-- <div
              :class="[
                step == 0 ? 'img-edit-btn-box-none' : 'img-edit-btn-box',
              ]"
              style="width: max-content"
              @click="exportMaskImage"
            >
              導出
            </div> -->
      </div>
      <div class="img-edit-btn-right"></div>
    </div>
    <div class="img-edit-box-content">
      <div class="img-preview-container" v-if="currentImgEdit == 'all'">
        <img class="img-background" :src="currentImgUrl" />
      </div>
      <div
        v-if="currentImgEdit != 'all'"
        ref="canvas_panelRef"
        class="img-preview-container"
      >
        <!-- <img ref="currentImgUrlRef" v-show="false" class="img-background" src="@/assets/image/test.png" /> -->
        <div class="img-preview-container-box" ref="imgPreviewContainerRef">
          <canvas ref="currentImgUrlCanvasRef"></canvas>
          <canvas ref="currentMaskCanvasRef"></canvas>
          <canvas ref="currentPanCanvasRef"></canvas>
        </div>
      </div>
    </div>
  </div>
</template>
 
<script setup>
import { nextTick } from "vue";
import { encryptText, decryptText } from "@/utils/crypto.js";
import { inject } from "vue";
const currentImgEdit = inject("currentImgEdit");
const showSend = inject("showSend");
const showSendRef = inject("showSendRef");
const props = defineProps({
  currentImgUrl: String,
});
 
// 更改圖片編輯狀態(tài)
// canvas相關代碼
const canvas_panelRef = ref();
const imgPreviewContainerRef = ref();
const currentImgUrlCanvasRef = ref();
const currentMaskCanvasRef = ref();
const currentPanCanvasRef = ref();
let context = null; //背景圖
let paintingContext = null; //paintingContext
let panContext = null; //panContext
let painting = false;
const brushSize = ref(5); // 筆刷大小
let mouseX = 0; // 鼠標 X 坐標
let mouseY = 0; // 鼠標 Y 坐標
let lastX = 0;
let lastY = 0;
let ratio = 0;
const canvasRect = ref({ top: 0, left: 0, width: 0, height: 0 });
const circleDiameter = ref(50); // 圓圈直徑
const maxDiameter = 100; // 最大直徑
const minDiameter = 30; // 最小直徑
let isPanLeave = true;
let tempCanvas = document.createElement("canvas");
let tempContext = tempCanvas.getContext("2d");
let history = ref([]); // 存儲畫布的歷史狀態(tài)
let step = ref(0); // 當前狀態(tài)的索引,初始為 -1 表示沒有歷史記錄
 
const clickCircleDiameter = () => {
  console.log("clickCircleDiameter");
  mouseX = currentPanCanvasRef.value.width / 2;
  mouseY = currentPanCanvasRef.value.height / 2;
  drawCircle();
};
const changeCircleDiameter = () => {
  console.log("changeCircleDiameter");
  panContext.clearRect(
    0,
    0,
    currentPanCanvasRef.value.width,
    currentPanCanvasRef.value.height
  );
};
const inputCircleDiameter = () => {
  console.log("inputCircleDiameter");
  drawCircle();
};
 
const canvasOffset = {
  left: 0,
  top: 0,
};
// 獲取canvas的偏移值
function getCanvasOffset() {
  const rect = currentMaskCanvasRef.value.getBoundingClientRect();
  canvasOffset.left =
    rect.left * (currentMaskCanvasRef.value.width / rect.width); // 兼容縮放場景
  canvasOffset.top =
    rect.top * (currentMaskCanvasRef.value.height / rect.height);
  console.log("canvasOffset", canvasOffset);
}
// 計算當前鼠標相對于canvas的坐標
function calcRelativeCoordinate(x, y) {
  return {
    x: x - canvasOffset.left,
    y: y - canvasOffset.top,
  };
}
 
// 存儲數(shù)據(jù)
function saveState(data) {
  // 如果當前 step 不是最后一個狀態(tài),則刪除之后的所有狀態(tài)
  if (step.value < history.value.length - 1) {
    history.value = history.value.slice(0, step.value + 1);
  }
  // 將新狀態(tài)添加到歷史數(shù)組中
  history.value.push(data);
  step.value++; // 更新 step
}
 
function moveCallback(event) {
  if (!painting) {
    return;
  }
  const { clientX, clientY } = event;
  const { x, y } = calcRelativeCoordinate(clientX, clientY);
  paintingContext.lineTo(x, y);
  paintingContext.stroke();
}
function updateCanvasOffset() {
  getCanvasOffset(); // 重新計算畫布的偏移值
}
 
// 繪制圓圈
const drawCircle = () => {
  if (!panContext) return;
  // 清空 Canvas
  panContext.clearRect(
    0,
    0,
    currentPanCanvasRef.value.width,
    currentPanCanvasRef.value.height
  );
  if (mouseX < 0 || mouseY < 0) {
    return;
  }
  // 繪制空心圓圈
  panContext.beginPath();
  panContext.arc(mouseX, mouseY, circleDiameter.value / 2, 0, Math.PI * 2);
  panContext.strokeStyle = "#ffffff"; // 邊框顏色
  panContext.lineWidth = 2; // 邊框寬度
  panContext.stroke();
};
// 動畫循環(huán)
const animate = () => {
  if (!isPanLeave) {
    drawCircle();
  }
  requestAnimationFrame(animate);
};
 
function downCallback(event) {
  console.log("222222222222222221111111");
  event.preventDefault(); // 阻止默認行為
  event.stopPropagation(); // 阻止事件冒泡
  showSend.value = false;
  // 先保存之前的數(shù)據(jù),用于撤銷時恢復(繪制前保存,不是繪制后再保存)
  // 初始化臨時畫布
  tempCanvas.width = currentMaskCanvasRef.value.width;
  tempCanvas.height = currentMaskCanvasRef.value.height;
  tempContext.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
  const data = paintingContext.getImageData(
    0,
    0,
    currentMaskCanvasRef.value.width,
    currentMaskCanvasRef.value.height
  );
  // 記錄起始點
  lastX = mouseX;
  lastY = mouseY;
  // 繪制實心圓圈
  paintingContext.beginPath();
  paintingContext.arc(mouseX, mouseY, circleDiameter.value / 2, 0, Math.PI * 2);
  paintingContext.fillStyle = "rgba(0, 119, 255, 0.5)";
  // 填充圓形
  paintingContext.fill();
  painting = true;
}
// 監(jiān)聽鼠標移動
const handleMouseMove = (event) => {
  isPanLeave = false;
  const rect = canvasRect.value;
  mouseX = event.clientX - rect.left;
  mouseY = event.clientY - rect.top;
  if (!painting || (mouseX == lastX && mouseY == lastY)) {
    return;
  }
  // 設置混合模式
  paintingContext.globalCompositeOperation = "xor";
  // 直接在主畫布上繪制線條
  paintingContext.beginPath();
  paintingContext.moveTo(lastX, lastY);
  paintingContext.lineTo(mouseX, mouseY);
  paintingContext.strokeStyle = "rgba(0, 119, 255, 0.5)"; // 固定透明度
  paintingContext.lineWidth = circleDiameter.value; // 使用 circleDiameter 作為線條寬度
  paintingContext.lineCap = "round"; // 設置線條末端為圓形
  paintingContext.stroke();
  // 更新上一個點的位置
  lastX = mouseX;
  lastY = mouseY;
};
const handleMouseUp = () => {
  if (painting) {
    // 保存當前畫布狀態(tài)
    const data = paintingContext.getImageData(
      0,
      0,
      currentMaskCanvasRef.value.width,
      currentMaskCanvasRef.value.height
    );
    saveState(data); // 調用 saveState 函數(shù)保存狀態(tài)
    // 繪制結束,清空臨時畫布
    tempContext.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
    painting = false;
  }
  showSendRef.value.style.top = `${event.y}px`;
  showSend.value = true;
};
const handleMouseLeave = () => {
  isPanLeave = true;
  panContext.clearRect(
    0,
    0,
    currentPanCanvasRef.value.width,
    currentPanCanvasRef.value.height
  );
};
function undo() {
  if (step.value > 0) {
    step.value--; // 回退到上一步
    const state = history.value[step.value];
    paintingContext.putImageData(state, 0, 0); // 恢復狀態(tài)
  }
}
function redo() {
  if (step.value < history.value.length - 1) {
    step.value++; // 前進一步
    const state = history.value[step.value];
    paintingContext.putImageData(state, 0, 0); // 恢復狀態(tài)
  }
}
function clearCanvas() {
  paintingContext.clearRect(
    0,
    0,
    currentMaskCanvasRef.value.width,
    currentMaskCanvasRef.value.height
  );
  // 存儲最新的歷史記錄
  const data = paintingContext.getImageData(
    0,
    0,
    currentMaskCanvasRef.value.width,
    currentMaskCanvasRef.value.height
  );
  history.value = [data]; // 清空歷史數(shù)組
  step.value = 0; // 重置 step
}
 
function createMaskImage() {
  // 創(chuàng)建一個臨時 Canvas
  const tempCanvas = document.createElement("canvas");
  console.log('ratioratio',ratio)
  tempCanvas.width = currentMaskCanvasRef.value.width / ratio;
  tempCanvas.height = currentMaskCanvasRef.value.height / ratio;
  const tempContext = tempCanvas.getContext("2d");
 
  // 將主 Canvas 的內容繪制到臨時 Canvas 上
  tempContext.drawImage(
    currentMaskCanvasRef.value,
    0,
    0,
    currentMaskCanvasRef.value.width,
    currentMaskCanvasRef.value.height,
    0,
    0,
    tempCanvas.width,
    tempCanvas.height
  );
 
  // 獲取臨時 Canvas 的像素數(shù)據(jù)
  const imageData = tempContext.getImageData(
    0,
    0,
    tempCanvas.width,
    tempCanvas.height
  );
  const data = imageData.data;
 
  // 遍歷像素數(shù)據(jù),將非透明像素設置為黑色,透明像素設置為白色
  for (let i = 0; i < data.length; i += 4) {
    const alpha = data[i + 3]; // 透明度通道
    if (alpha > 0) {
      // 非透明區(qū)域(涂抹的區(qū)域)
      data[i] = 0; // R
      data[i + 1] = 0; // G
      data[i + 2] = 0; // B
      data[i + 3] = 255; // A(不透明)
    } else {
      // 透明區(qū)域(背景)
      data[i] = 255; // R
      data[i + 1] = 255; // G
      data[i + 2] = 255; // B
      data[i + 3] = 255; // A(不透明)
    }
  }
 
  // 將處理后的像素數(shù)據(jù)放回臨時 Canvas
  tempContext.putImageData(imageData, 0, 0);
 
  // 導出圖片
  const image = tempCanvas.toDataURL("image/png");
  return image;
}
 
 
const changeEditStatus = (type) => {
  currentImgEdit.value = type;
  nextTick(() => {
    function resetCanvas() {
      // 創(chuàng)建一個 Image 對象
      let img = new Image();
      img.src = props.currentImgUrl;
      // 等待圖片加載完成
      img.onload = () => {
        const imgAspectRatio = img.width / img.height;
        // imgPreviewContainerRef
        let maxWidth = 0;
        let maxHeight = 0;
        let style = window.getComputedStyle(canvas_panelRef.value);
        // 獲取上內邊距
        let paddingTop = parseInt(style.paddingTop, 10);
        let paddingRight = parseInt(style.paddingRight, 10);
        let paddingBottom = parseInt(style.paddingBottom, 10);
        let paddingLeft = parseInt(style.paddingLeft, 10);
        maxWidth =
          canvas_panelRef.value.clientWidth - paddingRight - paddingLeft;
        maxHeight =
          canvas_panelRef.value.clientHeight - paddingTop - paddingBottom;
        let containerWidth = img.width; // 容器初始寬度
        let containerHeight = img.height; // 容器初始高度
        // 根據(jù)圖片比例調整容器寬高
          ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
          containerWidth = img.width * ratio;
          containerHeight = img.height * ratio;
        // 設置容器寬高
        imgPreviewContainerRef.value.style.width = containerWidth + "px";
        imgPreviewContainerRef.value.style.height = containerHeight + "px";
 
        // 設置 canvas 的寬高與容器一致
        currentImgUrlCanvasRef.value.width = containerWidth;
        currentImgUrlCanvasRef.value.height = containerHeight;
        currentMaskCanvasRef.value.width = containerWidth;
        currentMaskCanvasRef.value.height = containerHeight;
        currentPanCanvasRef.value.width = containerWidth;
        currentPanCanvasRef.value.height = containerHeight;
        context = currentImgUrlCanvasRef.value.getContext("2d", {
          willReadFrequently: true,
        });
        paintingContext = currentMaskCanvasRef.value.getContext("2d", {
          willReadFrequently: true,
        });
        panContext = currentPanCanvasRef.value.getContext("2d", {
          willReadFrequently: true,
        });
        // 初始位置在 Canvas 中心
        const rect = currentPanCanvasRef.value.getBoundingClientRect();
        canvasRect.value = rect;
        mouseX = -200;
        mouseY = -200;
        // mouseX.value = currentPanCanvasRef.value.width / 2;
        // mouseY.value = currentPanCanvasRef.value.height / 2;
        context.drawImage(img, 0, 0, containerWidth, containerHeight);
        // 存儲最新的歷史記錄
        const data = paintingContext.getImageData(
          0,
          0,
          currentMaskCanvasRef.value.width,
          currentMaskCanvasRef.value.height
        );
        history.value = [data];
        step.value = 0; // 更新 step
      };
      getCanvasOffset(); // 更新畫布位置
    }
 
    resetCanvas();
    window.addEventListener("resize", resetCanvas);
    window.addEventListener("scroll", updateCanvasOffset); // 添加滾動條滾動事件監(jiān)聽器
    getCanvasOffset();
    // paintingContext.lineGap = "round";
    // paintingContext.lineJoin = "round";
 
    // currentMaskCanvasRef.value.addEventListener("mousedown", downCallback);
    // currentMaskCanvasRef.value.addEventListener("mousemove", moveCallback);
    // currentMaskCanvasRef.value.addEventListener("mouseleave", closePaint);
 
    animate();
    // 添加事件監(jiān)聽
    currentPanCanvasRef.value.addEventListener("mousedown", downCallback);
    currentPanCanvasRef.value.addEventListener("mousemove", handleMouseMove);
    currentPanCanvasRef.value.addEventListener("mouseup", handleMouseUp);
    currentPanCanvasRef.value.addEventListener("mouseleave", handleMouseLeave);
  });
};
 
import { defineEmits } from "vue";
 
const emits = defineEmits(["quoteImgEditChange",'downloadBase64','closeImgEditVisible']);
 
const quoteImgEditChange = () => {
  emits("quoteImgEditChange");
};
const downloadBase64 = () => {
  emits("downloadBase64");
};
const closeImgEditVisible = () => {
  emits("closeImgEditVisible");
};
 
defineExpose({
    currentPanCanvasRef,
    createMaskImage,
    changeEditStatus
});
</script>
 
<style lang="scss" scoped>
.img-edit-box {
  max-width: 700px;
  flex: 1;
  // background: #e5e9fa;
  background: #ffffff;
  align-items: center;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  position: relative;
  box-shadow: 0 6px 10px 0 rgba(42, 60, 79, 0.1);
  transition: border-radius 0.4s ease-in-out;
 
  .img-edit-box-top {
    align-items: center;
    background: #ffffff;
    border-bottom: 0.5px solid rgba(0, 0, 0, 0.08);
    display: flex;
    flex-shrink: 0;
    // gap: 24px;
    height: 56px;
    padding: 0 16px;
    width: 100%;
    justify-content: flex-start;
    .img-edit-btn-slider {
      width: 80px;
      :deep(.el-slider) {
        height: unset;
      }
      :deep(.el-slider__button) {
        width: 15px;
        height: 15px;
        box-shadow:
          0 4px 6px rgba(0, 0, 0, 0.1),
          0 0 1px rgba(0, 0, 0, 0.3);
        border: none;
      }
      :deep(.el-slider__bar) {
        background-color: #242525;
      }
    }
    .img-edit-btn-box-none {
      display: flex;
      align-items: center;
      cursor: not-allowed;
      padding: 6px 8px;
      color: #d9d9d9;
      .chexiao-icon {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/have_chexiao.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
        padding: 6px 6px;
      }
      .huanyuan-icon {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/have_chexiao.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
        padding: 6px 6px;
        transform: scaleX(-1);
      }
    }
    .img-edit-btn-box {
      display: flex;
      align-items: center;
      padding: 6px 8px;
      cursor: pointer;
      .img-edit-btn-zhineng {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/zhineng.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
      }
      .img-edit-btn-chonghui {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/chonghui.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
      }
      .img-edit-btn-kuotu {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/kuotu.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
      }
      .img-edit-btn-cachu {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/cachu.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
      }
      .img-edit-btn-download {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/download.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
      }
      .img-edit-btn-text {
        // font-family: "Ali_Regular";
        margin-left: 4px;
        font-size: 14px;
      }
      .chexiao-icon {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/chexiao.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
        padding: 6px 6px;
        cursor: pointer;
      }
      .huanyuan-icon {
        width: 16px;
        height: 16px;
        background: url("../../../assets/image/chat/imageEdit/editInside/chexiao.png")
          no-repeat;
        background-size: 100% 100%;
        vertical-align: middle;
        padding: 6px 6px;
        cursor: pointer;
        transform: scaleX(-1);
      }
    }
    .close-box {
      padding: 6px;
    }
    .img-edit-btn-box:hover {
      background: rgba(0, 0, 0, 0.04);
      border-radius: 8px;
    }
    .img-edit-btn-right {
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .divide-line {
      background: rgba(0, 0, 0, 0.08);
      display: inline-block;
      flex-shrink: 0;
      height: 16px;
      margin: 0 4px 0 6px;
      width: 1px;
    }
    .close-icon {
      width: 16px;
      height: 16px;
      background: url("../../../assets/image/chat/imageEdit/editInside/close.png")
        no-repeat;
      background-size: 100% 100%;
      vertical-align: middle;
      padding: 6px 6px;
      cursor: pointer;
    }
    .back-icon {
      width: 16px;
      height: 16px;
      background: url("../../../assets/image/chat/imageEdit/editInside/back.png")
        no-repeat;
      background-size: 100% 100%;
      vertical-align: middle;
      padding: 6px 6px;
      cursor: pointer;
    }
    .to-right {
      margin-left: auto;
    }
  }
  .flex-center {
    justify-content: center;
    .img-edit-btn-left {
      flex: 1;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .img-edit-btn-center {
      flex: 1;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .img-edit-btn-right {
      flex: 1;
    }
    .to-left {
      margin-right: auto;
    }
  }
  .img-edit-box-content {
    position: relative;
    width: 100%;
    height: 100%;
    .img-preview-container {
      align-items: center;
      display: flex;
      justify-content: center;
      box-sizing: border-box;
      height: 100%;
      padding: 40px;
      width: 100%;
      .img-preview-container-box {
        position: relative;
        width: 100%;
        height: 100%;
        cursor: none;
        canvas {
          padding: 0px;
          margin: 0px;
          border: 0px;
          background: transparent;
          position: absolute;
          top: 0px;
          left: 0px;
          display: block;
        }
      }
      .img-background {
        border-radius: 10px;
        display: block;
        max-height: 100%;
        max-width: 100%;
        -o-object-fit: contain;
        object-fit: contain;
      }
    }
  }
}
</style>

總結

到此這篇關于前端Vue3圖像編輯功能實現(xiàn)的文章就介紹到這了,更多相關前端Vue3圖像編輯功能內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • vue-cli隨機生成port源碼的方法

    vue-cli隨機生成port源碼的方法

    這篇文章主要介紹了vue-cli隨機生成port源碼的方法,文中給大家介紹了vue 隨機色生成方法,需要的朋友可以參考下
    2019-09-09
  • vue開發(fā)環(huán)境配置跨域的方法步驟

    vue開發(fā)環(huán)境配置跨域的方法步驟

    本文介紹了使用vue-cli搭建的項目在開發(fā)時配置跨域,上線后不做任何任何修改,接口也可以訪問,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • vue 實現(xiàn)axios攔截、頁面跳轉和token 驗證

    vue 實現(xiàn)axios攔截、頁面跳轉和token 驗證

    這篇文章主要介紹了vue 實現(xiàn)axios攔截、頁面跳轉和token 驗證,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • Vue項目如何關閉語法檢查

    Vue項目如何關閉語法檢查

    這篇文章主要介紹了Vue項目如何關閉語法檢查問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 調用createApp?時Vue工作過程原理

    調用createApp?時Vue工作過程原理

    這篇文章主要為大家介紹了調用createApp?時Vue工作過程原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • vue使用echarts實現(xiàn)中國地圖和點擊省份進行查看功能

    vue使用echarts實現(xiàn)中國地圖和點擊省份進行查看功能

    這篇文章主要介紹了vue使用echarts實現(xiàn)中國地圖和點擊省份進行查看功能,本文通過實例代碼給大家詳細講解,對vue echarts 中國地圖相關知識感興趣的朋友一起看看吧
    2022-12-12
  • 干貨!教大家如何選擇Vue和React

    干貨!教大家如何選擇Vue和React

    Vue和React之間如何選擇,這篇文章主要為大家詳細介紹了Vue和React兩者之間的相同之處,教大家該如何進行選擇,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Vue項目依賴包安裝及配置過程

    Vue項目依賴包安裝及配置過程

    這篇文章主要介紹了Vue項目依賴包安裝及配置過程,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • vue中異步函數(shù)async和await的用法說明

    vue中異步函數(shù)async和await的用法說明

    這篇文章主要介紹了vue中異步函數(shù)async和await的用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 深入了解vue2與vue3的生命周期對比

    深入了解vue2與vue3的生命周期對比

    這篇文章主要為大家介紹了vue2與vue3的生命周期對比,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12

最新評論