《Nuxt.js 实战:从放弃到入门》五、还在为图片裁剪发愁?用它吧!

在这里插入图片描述

功能说明

  1. 设计目标:图片裁剪功能页面旨在实现自由裁剪、固定比例裁剪等丰富功能,为用户提供高效、便捷的图片处理体验。页面使用 Element Plus 组件库搭建,确保界面美观且交互易用,提升用户操作的流畅性和满意度。
  2. 页面功能
  • 图片上传区域:支持用户通过拖拽或点击的方式上传图片,仅支持 JPG 和 PNG 格式,文件大小限制为 10MB。
  • 裁剪预览区域:展示上传的图片,并提供可视化的裁剪操作界面,方便用户实时预览裁剪效果。
  • 裁剪参数设置面板:用户可在此设置裁剪比例,包括自由裁剪以及 1:1、4:3、16:9 等常见固定比例,还能进行向左旋转、向右旋转、水平翻转、垂直翻转等操作。

第三方库集成

实现图片裁剪功能依赖cropperjs库,使用以下命令进行安装:

npm install cropperjs

20250215022545237892.png

代码实现

  1. 创建页面文件:在项目中创建crop.vue文件,用于实现图片裁剪功能页面。
<template>
  <div class="container">
    <el-card>
      <template #header>
        <div class="text-center">
          <span class="text-xl font-medium">图片裁剪</span>
        </div>
      </template>

      <!-- 图片上传区域 -->
      <el-upload
          class="upload-area"
          drag
          :auto-upload="false"
          :show-file-list="false"
          accept="image/*"
          :on-change="handleFileSelect"
      >
        <template #trigger>
          <div v-if="!imageUrl && !isLoading && !errorMessage" class="text-center">
            <el-icon class="el-icon--upload mx-auto block"><upload-filled /></el-icon>
            <div class="el-upload__text">
              拖拽图片到此处或 <em>点击上传</em>
            </div>
            <div class="text-sm text-gray-400 mt-2">支持 JPG、PNG 格式</div>
          </div>
        </template>
      </el-upload>

      <div v-if="isLoading" class="text-center">
        <el-icon class="is-loading" :size="24"><loading /></el-icon>
        <div class="text-gray-600">正在处理图片...</div>
      </div>

      <div v-else-if="errorMessage" class="text-center">
        <el-alert
            :title="errorMessage"
            type="error"
            :closable="false"
        />
        <el-button type="primary" @click="errorMessage = ''">
          重试
        </el-button>
      </div>

      <div v-else-if="imageUrl" class="image-container">
        <!-- 裁剪区域 -->
        <img ref="imageRef" :src="imageUrl" alt="Crop Image" class="cropper" />

        <!-- 裁剪控制面板 -->
        <el-form class="mt-4">
          <el-form-item label="裁剪比例">
            <el-radio-group v-model="aspectRatioOption" @change="handleAspectRatioChange">
              <el-radio-button value="free">自由裁剪</el-radio-button>
              <el-radio-button value="1:1">1:1</el-radio-button>
              <el-radio-button value="4:3">4:3</el-radio-button>
              <el-radio-button value="16:9">16:9</el-radio-button>
            </el-radio-group>
          </el-form-item>

          <el-form-item>
            <el-button-group>
              <el-button @click="rotateLeft">
                <el-icon><refresh-left /></el-icon>
                向左旋转
              </el-button>
              <el-button @click="rotateRight">
                <el-icon><refresh-right /></el-icon>
                向右旋转
              </el-button>
              <el-button @click="flipHorizontal">
                <el-icon><sort /></el-icon>
                水平翻转
              </el-button>
              <el-button @click="flipVertical">
                <el-icon><sort /></el-icon>
                垂直翻转
              </el-button>
            </el-button-group>
          </el-form-item>

          <el-button
              type="primary"
              @click="cropImage"
          >
            下载裁剪后的图片
          </el-button>
        </el-form>
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, nextTick } from 'vue'
import { UploadFilled, Loading, RefreshLeft, RefreshRight, Sort } from '@element-plus/icons-vue'
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'

const imageUrl = ref('')
const isLoading = ref(false)
const errorMessage = ref('')
const imageRef = ref(null)
const cropper = ref(null)
const aspectRatioOption = ref('free')
const aspectRatio = ref(NaN)

// 文件大小限制(10MB)
const MAX_FILE_SIZE = 10 * 1024 * 1024
// 支持的图片格式
const SUPPORTED_FORMATS = ['image/jpeg', 'image/png']

// 验证文件
const validateFile = (file) => {
  if (!SUPPORTED_FORMATS.includes(file.type)) {
    errorMessage.value = '不支持的文件格式'
    return false
  }
  if (file.size > MAX_FILE_SIZE) {
    errorMessage.value = '文件大小不能超过10MB'
    return false
  }
  return true
}

// 处理文件选择
const handleFileSelect = async (uploadFile) => {
  const file = uploadFile.raw
  if (!validateFile(file)) return

  isLoading.value = true
  errorMessage.value = ''

  try {
    const reader = new FileReader()
    reader.onload = (e) => {
      imageUrl.value = e.target.result
      // 确保图片加载完成后再初始化裁剪器
      nextTick(() => {
        cropper.value = new Cropper(imageRef.value, {
          aspectRatio: aspectRatio.value,
          viewMode: 2,
          guides: true,
          background: true,
          rotatable: true,
          scalable: true,
          zoomable: true,
          center: true,
          autoCrop: true,
          minContainerWidth: 500,
          minContainerHeight: 400,
        })
      })
      isLoading.value = false
    }
    reader.onerror = () => {
      console.error('图片加载失败');
      errorMessage.value = '图片加载失败'
      isLoading.value = false
    }
    reader.readAsDataURL(file)
  } catch (error) {
    errorMessage.value = '图片处理失败'
    isLoading.value = false
  }
}

// 处理裁剪比例变化
const handleAspectRatioChange = (value) => {
  let ratio
  switch (value) {
    case 'free':
      ratio = NaN
      console.log(ratio)
      break
    case '1:1':
      ratio = 1
      console.log(ratio)
      break
    case '4:3':
      ratio = 4/3
      console.log(ratio)
      break
    case '16:9':
      ratio = 16/9
      console.log(ratio)
      break
  }
  aspectRatio.value = ratio
  // 直接调用裁剪器的setAspectRatio方法来更新比例
  if (cropper.value) {
    cropper.value.setAspectRatio(ratio)
  }
}

// 向左旋转
const rotateLeft = () => {
  if (cropper.value) {
    cropper.value.rotate(-90)
  }
}

// 水平翻转
const flipHorizontal = () => {
  if (cropper.value) {
    console.log(cropper.value)
    cropper.value.scaleX(-cropper.value.getImageData().scaleX || -1)
  }
}

// 垂直翻转
const flipVertical = () => {
  if (cropper.value) {
    console.log(cropper.value)
    cropper.value.scaleY(-cropper.value.getImageData().scaleY || -1)
  }
}

// 向右旋转
const rotateRight = () => {
  if (cropper.value) {
    cropper.value.rotate(90)
  }
}

// 裁剪并下载图片
const cropImage = () => {
  if (cropper.value) {
    const canvas = cropper.value.getCroppedCanvas()
    const link = document.createElement('a')
    link.download = 'cropped-image.png'
    link.href = canvas.toDataURL('image/png')
    link.click()
  }
}
</script>

<style scoped>
.image-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  margin-top: 20px;
}

.cropper {
  width: 100%;
  height: 500px;
  background-color: #f5f5f5;
  min-height: 400px;
}

.upload-area {
  width: 100%;
  padding: 20px;
  border: 2px dashed #dcdfe6;
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.3s;
}

.upload-area:hover {
  border-color: #409eff;
}

.el-icon--upload {
  font-size: 48px;
  color: #909399;
  margin-bottom: 16px;
}

.el-upload__text {
  color: #606266;
  font-size: 14px;
  text-align: center;
}

.el-upload__text em {
  color: #409eff;
  font-style: normal;
}

el-form {
  width: 100%;
  margin-top: 20px;
}

el-button {
  margin-top: 20px;
}
</style>
  1. 添加到导航栏:在Header.vue中添加图片裁剪页面的导航,方便用户在不同图片处理功能页面间快速切换。
<el-col :span="4">
          <el-menu mode="horizontal" router :ellipsis="false">
            <el-menu-item index="/resize">
              <span>图片调整</span>
            </el-menu-item>
            <el-menu-item index="/compress">
              <span>图片压缩</span>
            </el-menu-item>
            <el-menu-item index="/divide">
              <span>图片分割</span>
            </el-menu-item>
            <el-menu-item index="/crop">
              <span>图片裁剪</span>
            </el-menu-item>
          </el-menu>
        </el-col>

20250215022953454572.png

效果展示

20250215023048912879.png

代码托管地址:https://github.com/outeasy/outeasy/releases/tag/v0.5

posted @   薛尧笔记  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
薛尧的博客
点击右上角即可分享
微信分享提示