前端开发攻略---Vue实现图像裁剪功能,支持用户通过图形界面进行裁剪区域的调整,最终生成裁剪后的图像。

目录

1、演示

2、实现原理

3、实现功能

4、代码


1、演示

2、实现原理

这里有详细介绍前端开发攻略---图片裁剪上传的原理-CSDN博客

3、实现功能

  • 上传图像

    • 用户选择文件后,changeFile 方法读取文件内容并将其转换为 Data URL,这样可以在页面上显示图像。
  • 裁剪区域显示与交互

    • 使用 cutContainerStyle 计算并设置裁剪区域的样式(位置和尺寸)。
    • 通过 handleMouseDownhandleMouseMovehandleMouseUp 方法处理用户对裁剪区域的拖动操作。
    • elMapFn 对象包含处理不同裁剪区域调整(例如四角、边界点)的具体逻辑。
    • @mousemove@wheel 事件处理函数允许用户在调整裁剪区域的同时进行拖动和缩放操作。
  • 裁剪图像

    • cutImageBtn 方法通过以下步骤裁剪图像:
      1. 获取裁剪参数:获取裁剪区域的位置信息和尺寸。
      2. 计算缩放比例:根据图像的显示尺寸和原始尺寸计算缩放比例。
      3. 裁剪图像 :创建一个 canvas 元素,使用 drawImage 方法在 canvas 上绘制裁剪区域的图像部分。
      4. 生成裁剪后的图像:将 canvas 内容转换为 Blob 对象,再通过 FileReader 转换为 Data URL,显示在页面上。

4、代码

本案例采用Vue3框架实现。可以直接复制全部代码,放到一个干净的vue文件中即可

html 复制代码
<template>
  <div class="app" @mouseup="handleMouseUp">
    <div>
      <input type="file" @change="changeFile" /> <br />
      <br />
      <button @click="cutImageBtn" v-if="originImage">确认裁剪</button>
      <br />
      <img :src="cutAfterUrl" alt="" />
    </div>
    <div class="previewContainer" ref="previewContainer" @mousemove="handleMouseMove" v-if="originImage" @wheel="handleWheel">
      <img :src="originImage" alt="" ref="originImageEl" />
      <div
        class="cutContainer"
        @mousedown="handleMouseDown"
        action="cutContainer"
        ref="cutContainer"
        :style="{
          transform: `translateX(${cutContainerStyle.translateX}px) translateY(${cutContainerStyle.translateY}px)`,
          width: `${cutContainerStyle.width}px`,
          height: `${cutContainerStyle.height}px`,
        }"
      >
        <img
          :src="originImage"
          alt=""
          :style="{
            transform: `translateX(-${cutContainerStyle.translateX}px) translateY(-${cutContainerStyle.translateY}px)`,
          }"
        />
        <span class="point lt" action="lt"></span>
        <span class="point rt" action="rt"></span>
        <span class="point lb" action="lb"></span>
        <span class="point rb" action="rb"></span>
        <span class="point tm" action="tm"></span>
        <span class="point rm" action="rm"></span>
        <span class="point bm" action="bm"></span>
        <span class="point lm" action="lm"></span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
const originImage = ref('')
const originImageEl = ref(null)
const cutContainer = ref(null)
const cutAfterUrl = ref('')
const previewContainer = ref(null)
const cutContainerStyle = reactive({
  translateX: 50,
  translateY: 50,
  width: 400,
  height: 400,
})

const handleMouseUp = () => {
  elMapFn.isMove = false
}
const handleMouseDown = e => {
  e.preventDefault()
  elMapFn.isMove = true
  elMapFn.clickX = e.offsetX
  elMapFn.clickY = e.offsetY
  elMapFn.clickTarget = e.target.getAttribute('action')
}

const elMapFn = {
  isMove: false,
  clickX: 0,
  clickY: 0,
  clickTarget: null,
  cutContainer: function (e) {
    const { left, top, width, height } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left - this.clickX
    let y = e.clientY - top - this.clickY
    if (y <= 0) {
      y = 0
    }
    if (x <= 0) {
      x = 0
    }
    if (x >= width - cutContainer.value.offsetWidth) {
      x = width - cutContainer.value.offsetWidth
    }
    if (y >= height - cutContainer.value.offsetHeight) {
      y = height - cutContainer.value.offsetHeight
    }
    cutContainerStyle.translateX = x
    cutContainerStyle.translateY = y
  },
  lt: function (e) {
    const { left, top, width, height } = previewContainer.value.getBoundingClientRect()
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
    cutContainerStyle.translateY = y
    cutContainerStyle.height = h
  },
  rt: function (e) {
    const { left, top, height } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    cutContainerStyle.translateY = y
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  lb: function (e) {
    const { left, top, width } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  rb: function (e) {
    const { left, top } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  tm: function (e) {
    const { top, height } = previewContainer.value.getBoundingClientRect()
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    cutContainerStyle.translateY = y
    cutContainerStyle.height = h
  },
  rm: function (e) {
    const { left } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    cutContainerStyle.width = w
  },
  bm: function (e) {
    const { top } = previewContainer.value.getBoundingClientRect()
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.height = h
  },
  lm: function (e) {
    const { left, top, height, width } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
  },
}
const handleMouseMove = e => {
  if (!elMapFn.isMove || !elMapFn.clickTarget) return
  elMapFn[elMapFn.clickTarget] && elMapFn[elMapFn.clickTarget](e)
}
const changeFile = e => {
  const file = e.target.files[0]
  const fileReader = new FileReader()
  fileReader.onload = e => {
    originImage.value = e.target.result
  }
  fileReader.readAsDataURL(file)
}
const cutImageBtn = () => {
  const { translateX, translateY, width, height } = cutContainerStyle
  // 获取用户输入的裁剪位置
  const x = parseInt(translateX, 10)
  const y = parseInt(translateY, 10)
  const w = parseInt(width, 10)
  const h = parseInt(height, 10)
  // 获取图片的显示尺寸
  const displayWidth = originImageEl.value.clientWidth
  const displayHeight = originImageEl.value.clientHeight
  // 获取图片的原始尺寸
  const originalWidth = originImageEl.value.naturalWidth
  const originalHeight = originImageEl.value.naturalHeight
  // 计算缩放比例
  const xScale = originalWidth / displayWidth
  const yScale = originalHeight / displayHeight
  // 将裁剪位置转换为原始图像上的坐标
  const cropX = x * xScale
  const cropY = y * yScale
  const cropW = w * xScale
  const cropH = h * yScale
  // 创建一个 canvas 元素来进行裁剪
  const cvs = document.createElement('canvas')
  const ctx = cvs.getContext('2d')
  // 设置 canvas 大小与图像相同
  cvs.width = originalWidth
  cvs.height = originalHeight
  // 将图像绘制到 canvas 上
  ctx.drawImage(originImageEl.value, cropX, cropY, cropW, cropH, 0, 0, cvs.width, cvs.height)
  cvs.toBlob(blob => {
    // 拿到file对象 可以将裁剪好后的图片上传服务器
    const file = new File([blob], 'cut-png', { type: 'image/png' })
    const fileReader = new FileReader()
    fileReader.onload = e => {
      cutAfterUrl.value = e.target.result
    }
    fileReader.readAsDataURL(file)
  })
}
</script>

<style scoped>
.app {
  width: 100vw;
  height: 100vh;
  background-color: saddlebrown;
  display: flex;
  justify-content: center;
  align-items: center;
}
#cvs {
  width: 500px;
  height: 500px;
  border: 1px solid red;
}
.previewContainer {
  width: 500px;
  height: 500px;
  background-color: #000;
  box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 24px;
  overflow: hidden;
  position: relative;
  user-select: none;
}
.previewContainer > img {
  position: absolute;
  width: 500px;
  height: 500px;
  opacity: 0.5;
}
.previewContainer .cutContainer {
  width: 400px;
  height: 400px;
  border: 1px solid #266fff;
  overflow: hidden;
  position: relative;
}
.cutContainer:hover {
  cursor: move;
}
.cutContainer > img {
  position: absolute;
  width: 500px;
  height: 500px;
  z-index: -100;
}
.cutContainer::before {
  content: '';
  position: absolute;
  width: calc(100% / 3);
  height: 100%;
  border-left: 1px dashed #eee;
  border-right: 1px dashed #eee;
  left: 50%;
  margin-left: calc(100% / -3 / 2);
}

.cutContainer::after {
  content: '';
  position: absolute;
  width: 100%;
  height: calc(100% / 3);
  border-top: 1px dashed #eee;
  border-bottom: 1px dashed #eee;
  top: 50%;
  margin-top: calc(100% / -3 / 2);
  z-index: 999;
}
.point {
  display: inline-block;
  position: absolute;
  width: 5px;
  height: 5px;
  background-color: #266fff;
  z-index: 9999;
}

.point.lt {
  left: 0;
  top: 0;
}

.point.tm {
  left: 50%;
  transform: translateX(-50%);
}
.point.rt {
  right: 0;
}
.point.rm {
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}
.point.rb {
  right: 0;
  bottom: 0;
}
.point.bm {
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}
.point.lb {
  bottom: 0;
  left: 0;
}
.point.lm {
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}
.point.tm:hover,
.point.bm:hover {
  cursor: s-resize;
}
.point.lm:hover,
.point.rm:hover {
  cursor: w-resize;
}
.point.lt:hover,
.point.rb:hover {
  cursor: se-resize;
}
.point.rt:hover,
.point.lb:hover {
  cursor: ne-resize;
}
</style>
相关推荐
customer0810 分钟前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
清灵xmf11 分钟前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据18 分钟前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_3901617726 分钟前
防抖函数--应用场景及示例
前端·javascript
334554321 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx