《Nuxt.js 实战:从放弃到入门》三、超实用! 打造图片压缩神器

20250215213857395997.png

功能说明

  1. 设计风格:图片压缩功能页面将参考网络部分网站的设计风格,采用 Element Plus 组件进行开发,以确保界面美观且易用。
  2. 页面功能
  • 拖拽上传区域:支持用户将图片直接拖拽到指定区域进行上传,也可点击上传。
  • 图片压缩选项:通过滑块控制压缩质量,用户可根据需求调整。
  • 批量图片处理功能:支持同时上传和压缩多张图片。
  • 对比预览:提供压缩前后图片的对比预览,方便用户查看效果。

代码实现

  1. 创建页面文件:在pages目录下创建compress.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
          multiple
          :auto-upload="false"
          :show-file-list="true"
          accept="image/*"
          :on-change="handleFileSelect"
          :on-remove="handleFileRemove"
          :file-list="fileList"
      >
        <template #trigger>
          <div v-if="!isLoading" 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、WebP 格式</div>
          </div>
        </template>
      </el-upload>

      <!-- 压缩设置 -->
      <div v-if="fileList.length > 0" class="compression-settings">
        <el-form>
          <el-form-item label="压缩质量" style="min-width: 400px;">
            <el-row :gutter="20" style="width: 100%;">
              <el-col :span="18">
                <el-slider
                    v-model="quality"
                    :min="1"
                    :max="100"
                    :format-tooltip="value => `${value}%`"
                    @input="handleQualityChange"
                />
              </el-col>
              <el-col :span="5" :offset="1" class="text-right">
                <span class="text-gray-600">{{ quality }}%</span>
              </el-col>
            </el-row>
          </el-form-item>

          <el-button
              type="primary"
              @click="compressImages"
              :loading="isLoading"
              :disabled="fileList.length === 0"
          >
            开始压缩
          </el-button>
        </el-form>
      </div>

      <!-- 压缩结果列表 -->
      <div v-if="compressedFiles.length > 0" class="compression-results mt-6">
        <el-table :data="compressedFiles" style="width: 100%">
          <el-table-column label="文件名" prop="name" />
          <el-table-column label="原始大小">
            <template #default="{ row }">
              {{ formatFileSize(row.originalSize) }}
            </template>
          </el-table-column>
          <el-table-column label="压缩后大小">
            <template #default="{ row }">
              {{ formatFileSize(row.compressedSize) }}
            </template>
          </el-table-column>
          <el-table-column label="压缩率">
            <template #default="{ row }">
              {{ calculateCompressionRate(row) }}%
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200">
            <template #default="{ row }">
              <el-button-group>
                <el-button type="primary" link @click="downloadFile(row)">
                  下载
                </el-button>
                <el-button type="primary" link @click="previewFile(row)">
                  预览
                </el-button>
              </el-button-group>
            </template>
          </el-table-column>
        </el-table>

        <div class="mt-4 text-right">
          <el-button type="primary" @click="downloadAll" :disabled="compressedFiles.length === 0">
            下载全部
          </el-button>
        </div>
      </div>

      <!-- 预览对话框 -->
      <el-dialog
          v-model="previewDialogVisible"
          title="图片预览"
          width="80%"
          destroy-on-close
      >
        <div class="preview-container">
          <div class="preview-item">
            <h3 class="text-center mb-2">原图</h3>
            <el-image
                :src="previewOriginal"
                fit="contain"
                class="preview-image"
            />
          </div>
          <div class="preview-item">
            <h3 class="text-center mb-2">压缩后</h3>
            <el-image
                :src="previewCompressed"
                fit="contain"
                class="preview-image"
            />
          </div>
        </div>
      </el-dialog>
    </el-card>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { UploadFilled } from '@element-plus/icons-vue'
import imageCompression from 'browser-image-compression';

// 状态变量
const fileList = ref([])
const compressedFiles = ref([])
const quality = ref(80)
const isLoading = ref(false)
const previewDialogVisible = ref(false)
const previewOriginal = ref('')
const previewCompressed = ref('')

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

// 处理文件选择
const handleFileSelect = (uploadFile) => {
  const file = uploadFile.raw
  if (file && validateFile(file)) {
    fileList.value.push(uploadFile)
  }
}

// 处理文件删除
const handleFileRemove = (uploadFile) => {
  // 从fileList中删除文件
  const index = fileList.value.findIndex(file => file.uid === uploadFile.uid)
  if (index > -1) {
    fileList.value.splice(index, 1)
  }

  // 从compressedFiles中删除对应的压缩文件
  const compressedIndex = compressedFiles.value.findIndex(file => file.name === uploadFile.name)
  if (compressedIndex > -1) {
    compressedFiles.value.splice(compressedIndex, 1)
  }
}

// 验证文件
const validateFile = (file) => {
  if (!SUPPORTED_FORMATS.includes(file.type)) {
    ElMessage.error('请上传 JPG、PNG 或 WebP 格式的图片')
    return false
  }
  if (file.size > MAX_FILE_SIZE) {
    ElMessage.error('图片大小不能超过 10MB')
    return false
  }
  return true
}

// 处理质量变化
const handleQualityChange = () => {
  // 仅更新质量值,不执行压缩
  if (fileList.value.length === 0) return;
}

// 压缩图片
const compressImages = async () => {
  if (fileList.value.length === 0) return
  
  isLoading.value = true
  try {
    for (const file of fileList.value) {
      const result = await compressImage(file.raw)
      compressedFiles.value.push({
        name: file.name,
        originalSize: file.raw.size,
        compressedSize: result.size,
        originalUrl: URL.createObjectURL(file.raw),
        compressedUrl: URL.createObjectURL(result)
      })
    }
  } catch (error) {
    ElMessage.error('压缩过程中发生错误')
  } finally {
    isLoading.value = false
  }
}

// 压缩单个图片
const compressImage = async (file) => {
  const options = {
    maxSizeMB: 1,
    maxWidthOrHeight: 2048,
    useWebWorker: true,
    maxIteration: 10,
    quality: quality.value / 100,
  }

  try {
    const compressedFile = await imageCompression(file, options)
    return compressedFile
  } catch (error) {
    ElMessage.error('图片压缩失败')
    throw error
  }
}

// 使用完后释放 URL
compressedFiles.value.forEach(file => {
  URL.revokeObjectURL(file.compressedUrl)
  URL.revokeObjectURL(file.originalUrl)
})

// 格式化文件大小
const formatFileSize = (bytes) => {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1)
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

// 计算压缩率
const calculateCompressionRate = (file) => {
  const reduction = file.originalSize - file.compressedSize
  return Math.round((reduction / file.originalSize) * 100)
}

// 预览文件
const previewFile = (file) => {
  previewOriginal.value = file.originalUrl
  previewCompressed.value = file.compressedUrl
  previewDialogVisible.value = true
}

// 下载单个文件
const downloadFile = (file) => {
  const link = document.createElement('a')
  link.href = file.compressedUrl
  link.download = `compressed_${file.name}`
  link.click()
}

// 下载所有文件
const downloadAll = () => {
  compressedFiles.value.forEach(file => {
    downloadFile(file)
  })
}
</script>

<style scoped>
.compression-settings {
  margin-top: 20px;
}

.preview-container {
  display: flex;
  justify-content: space-between;
  gap: 20px;
}

.preview-item {
  flex: 1;
}

.preview-image {
  width: 100%;
  max-height: 500px;
  object-fit: contain;
}

.el-upload-list {
  margin-top: 20px;
}

.compression-results {
  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>
        </el-col>

20250214230510393014.png

  1. 第三方库集成:建议在图片压缩功能中集成browser - image - compression库来提升压缩效果。这是一个轻量级的 JavaScript 库,专门用于浏览器端的图片压缩,支持多种图片格式,并且可以自定义压缩参数。使用以下命令安装:
npm install browser-image-compression

图片压缩方法

// 压缩图片
const compressImages = async () => {
  if (fileList.value.length === 0) return
  
  isLoading.value = true
  try {
    for (const file of fileList.value) {
      const result = await compressImage(file.raw)
      compressedFiles.value.push({
        name: file.name,
        originalSize: file.raw.size,
        compressedSize: result.size,
        originalUrl: URL.createObjectURL(file.raw),
        compressedUrl: URL.createObjectURL(result)
      })
    }
  } catch (error) {
    ElMessage.error('压缩过程中发生错误')
  } finally {
    isLoading.value = false
  }
}

效果展示

20250214230811905524.png

上传图片后,点击压缩后

20250214230941850603.png

可以预览生成的图

20250214231102183929.png

预览的图下载来比较

20250214231222520950.png

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