前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >《Nuxt.js 实战:从放弃到入门》三、超实用! 打造图片压缩神器

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

作者头像
小码农薛尧
发布2025-02-20 10:36:39
发布2025-02-20 10:36:39
6700
代码可运行
举报
文章被收录于专栏:小码农薛尧
运行总次数:0
代码可运行
功能说明
  1. 设计风格:图片压缩功能页面将参考网络部分网站的设计风格,采用 Element Plus 组件进行开发,以确保界面美观且易用。
  2. 页面功能
  • 拖拽上传区域:支持用户将图片直接拖拽到指定区域进行上传,也可点击上传。
  • 图片压缩选项:通过滑块控制压缩质量,用户可根据需求调整。
  • 批量图片处理功能:支持同时上传和压缩多张图片。
  • 对比预览:提供压缩前后图片的对比预览,方便用户查看效果。

代码实现

  1. 创建页面文件:在pages目录下创建compress.vue文件。
代码语言:javascript
代码运行次数:0
复制

<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 格式的图片')
    returnfalse
  }
if (file.size > MAX_FILE_SIZE) {
    ElMessage.error('图片大小不能超过 10MB')
    returnfalse
  }
returntrue
}

// 处理质量变化
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)
returnparseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

// 计算压缩率
const calculateCompressionRate = (file) => {
const reduction = file.originalSize - file.compressedSize
returnMath.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中。
代码语言:javascript
代码运行次数:0
复制

    <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
20250214230510393014.png
  1. 第三方库集成:建议在图片压缩功能中集成browser - image - compression库来提升压缩效果。这是一个轻量级的 JavaScript 库,专门用于浏览器端的图片压缩,支持多种图片格式,并且可以自定义压缩参数。使用以下命令安装:
代码语言:javascript
代码运行次数:0
复制

npm install browser-image-compression

图片压缩方法

代码语言:javascript
代码运行次数:0
复制

// 压缩图片
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
20250214230811905524.png

上传图片后,点击压缩后

20250214230941850603.png
20250214230941850603.png

可以预览生成的图

20250214231102183929.png
20250214231102183929.png

预览的图下载来比较

20250214231222520950.png
20250214231222520950.png

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小码农薛尧 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码实现
  • 效果展示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档