предисловие
Сжатие изображений — очень распространенная операция во фронтенд-разработке, и существует три распространенных метода:
- Мисс UI сжата и представлена во внешнем интерфейсе (где я могу найти такую заботливую Мисс UI~)
- PS и другое программное обеспечение для сжатия изображений
- Сжатие изображений онлайн-сайтов (tinypng и т. д.)
Однако сегодня мы будем использовать js для самостоятельного написания настольного приложения для сжатия изображений.Скриншоты программы выглядят следующим образом:
Особенности приложения:
- Пакетное сжатие: вы можете настроить количество сжатых пакетов самостоятельно, на этот раз мы установили 100 (tinypng онлайн может сжимать до 20 за раз).
- Быстрое сжатие
- Качество сжатия такое же, как у tinypng.
Разговор о выборе технологий: Electron + Vue3 + Element plus
Электрон:
В настоящее время это популярный js-фреймворк для создания кроссплатформенных приложений. Раньше я также использовал электрон для разработки небольших приложений. Мне лично нравится электрон по трем причинам:
- Кроссплатформенность, разовая разработка, мультиплатформенность приложения;
- Низкая стоимость обучения и времени разработки, особенно для фронтенд-разработчиков;
- Встроенные часто используемые функциональные модули, такие как наше сжатие изображений, на этот раз используют встроенный модуль nativeImage электронного
Есть много преимуществ электрона, вы можете перейти на официальный сайт электрона, чтобы понять, что многие из наших часто используемых программ разработаны с использованием электрона, например, одно из рабочих программ наших инженеров-разработчиков: vs code
Конечно, есть и некоторые недостатки.Приложение немного больше после упаковки.Например, наше приложение для сжатия изображений на этот раз больше 50M после упаковки.Даже если упаковка оптимизирована, размер упаковки не будет маленьким.Это определяется базовой реализацией самого электрона.Да, я надеюсь, что чиновник сможет оптимизировать этот момент~
vue3:
Лично я предпочитаю версию 3.0 Composition API, но сейчас компания использует версию Vue2.x, и я планирую попрактиковаться с этим приложением~
Элемент Плюс:
Это в основном лень (закрывает лицо), готовые компоненты действительно ароматные~
функциональное мышление
Электронное ядро разделено на основной процесс и процесс рендеринга:
- Процесс рендеринга — это наша интерфейсная среда, а этот проект — одностраничное приложение, созданное vue;
- Основной процесс управляет процессом рендеринга и отвечает за взаимодействие с системой, которая является связующим звеном между процессом рендеринга и системой;
Для реализации функции сжатия изображения пользователь выбирает изображения пакетами на странице, отправляет путь к изображению основному процессу, основной процесс сжимает изображение и сохраняет изображение в указанном каталоге, возвращает статус успешного или неудачного сжатия. к процессу рендеринга, и страница подсказывает успех или неудачу. :
сборка проекта
Сначала нужно установить:
- node
- npm
- vue-cli
Далее создаем проект:
vue create <项目名称>
Затем введите информацию о проекте:
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, CSS Pre-processors
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? No
Установите vue-cli-plugin-electron-builder, выберите ^9.0.0 для электронной версии:
cd <项目目录>
vue add electron-builder
Стартовый проект:
npm run electron:serve
каталог проекта
В инициализированном проекте уже есть какие-то страницы, но они нам не нужны, упростим директорию проекта:
- dist_electron
- node_modules
- public
- index.html
- src
- router
- index.js
- styles
- base.scss
- utils
- utils.js
- compress-electron.js
- views
- ImageCompress.vue
- App.vue
- background.js
- main.js
- router
- babel.config.js
- package.json
- README.md
начать кодирование
Сначала устанавливаем element-plus
npm install element-plus --save
Ввести element-plus и base.scss в main.js (base.scss — это некоторые базовые стили и общие стили)
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
import './styles/base.scss'
createApp(App).use(router).use(ElementPlus).mount('#app')
Напишите маршрут: router/index.js
// router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import ImageCompress from '../views/ImageCompress.vue'
const routes = [{
path: '/',
name: 'ImageCompress',
component: ImageCompress,
}]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
В электронном ipcmian (основной процесс) и ipcrenderer (процесс рендеринга) отвечают за связь между основным процессом и процессом рендеринга. Это требует введения electronic.ipcrenderer на странице vue. Перед введением нам нужно его настроить в фоновом режиме.js электрона. Разрешить страницам интегрировать модули узлов
// background.js
const win = new BrowserWindow({
width: 800, // 应用界面宽度
height: 600, // 应用界面高度
webPreferences: {
nodeIntegration: true, //允许页面集成node模块
webSecurity: false,// 取消跨域限制
}
})
Теперь мы можем добавить на страницу модули узла, такие как электрон и путь, и использовать для импорта window.require("electro").
Теперь напишем интерфейс и логику приложения.В интерфейсных компонентах используется element-plus: слайдер-компонент el-slider используется для выбора качества сжатия, а компонент el-upload используется для выбора изображения.Структура страницы следующая:
<!-- 页面结构-->
<template>
<div class="tinypng-wrapper">
<div class="tips">
<p>1. 只能压缩 <span class="highlight">jpg/png</span> 格式图片;</p>
<p>2. 一次最多压缩<span class="highlight">100张</span>;</p>
<p>
3. 压缩后的文件会保存在<span class="highlight"
>最后一张图片路径下的image-compress文件夹</span
>中, 请留意成功后的提示;
</p>
<p>
4. image-compress文件夹中如果有同名文件,将被<span class="highlight"
>覆盖</span
>;
</p>
<p>5. 图片处理需要时间,点击压缩后请耐心等待片刻。</p>
</div>
<div class="header">
<span class="label">压缩质量</span>
<el-slider
class="slider"
v-model="quality"
:step="10"
:min="10"
:marks="marks"
show-stops
>
</el-slider>
</div>
<div class="header">
<el-input placeholder="保存文件目录" v-model="targetDir" disabled>
<template #prepend>图片保存目录</template>
</el-input>
<el-button style="margin-left: 24px" type="success" @click="handleSubmit">开始压缩</el-button>
</div>
<div class="tinypng-content">
<el-upload
class="upload-demo"
ref="upload"
accept=".jpg,.png"
multiple
:auto-upload="false"
:limit="maxFileNum"
:file-list="fileList"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-change="handleChangeFile"
action=""
list-type="picture-card"
>
<i class="el-icon-plus"></i>
</el-upload>
</div>
</div>
</template>
Далее идет запись логики страницы.После выбора пользователем файла и качества сжатия, создается директория хранения файла, а системный путь файла сохраняется в массиве, который передается основному процессу через ipcRenderer , а затем передается основному процессу для обработки изображения. После завершения обработки (или сбоя) страница отвечает статусом обработки, возвращаемым основным процессом:
- ipcRenderer.send(): отправить сообщение основному процессу (ipcMain)
- ipcRenderer.on(): ответ на сообщения, отправленные основным процессом (ipcMain)
// 页面逻辑
<script>
// electron ipcRenderer -- 与electron主进程通信
const { ipcRenderer } = window.require("electron")
// path模块,处理文件路径
const PATH = window.require("path");
import { onMounted, ref, onBeforeUnmount } from "vue";
import { ElMessage, ElNotification, ElLoading } from "element-plus";
// loading 实例
let loadingInstance = null;
export default {
setup() {
// 文件列表
const fileList = ref([]);
// 批量处理文件数量限制
const maxFileNum = ref(100);
// 图片选择组件
const upload = ref(null);
// 图片保存的目标目录
const targetDir = ref(null);
// 图片压缩质量
const quality = ref(50);
// 图片压缩质量选项
const marks = ref({
10: "10",
20: "20",
30: "30",
40: "40",
50: "50",
60: "60",
70: "70",
80: "80",
90: "90",
100: "100"
});
// 文件选择数量超出设定值时,弹出警告框
const handleExceed = (files, fileList) => {
ElMessage.warning({
message: `最多只能选择 ${ maxFileNum.value }个文件哦,当前选择了 ${files.length + fileList.length} 个文件`,
type: "warning"
});
};
// 文件改变事件,设置文件保存目录为当前目录下的image-compress文件夹,没有会创建,有同名文件会覆盖
const handleChangeFile = file => {
const parseUrl = PATH.parse(file.raw.path);
targetDir.value = parseUrl.dir + `${PATH.sep}image-compress`;
};
// 确认按钮,开始压缩
const handleSubmit = () => {
const uploadFiles = upload.value.uploadFiles;
// 验证是否选择了图片,没有选择弹出警告信息
if (!uploadFiles.length) {
ElNotification({
title: "警告",
message: "请先选择文件!",
type: "warning"
});
return false;
}
const dir = PATH.normalize(targetDir.value);
// 遍历出图片文件的路径
const fileList = [];
uploadFiles.map(item => item?.raw?.path && fileList.push(item.raw.path));
// 消息参数
const data = {
fileList,
quality: quality.value,
targetDir: dir
};
// 显示loading
loadingInstance = ElLoading.service({
background: "rgba(255,255,255,0.5)"
});
// 向主进程发送消息,消息中有:压缩质量、压缩保存目录、压缩文件的地址(数组)
ipcRenderer.send("compress-image", data);
};
onBeforeUnmount(() => {
loadingInstance = null;
});
// mounted 生命周期
onMounted(() => {
// 响应主进程推送的图片压缩状态,并弹框显示
ipcRenderer.on("compress-status", (event, arg) => {
ElNotification({
title: arg.success ? "成功" : "失败",
message: arg.success ? arg.msg : arg.reason,
type: arg.success ? "success" : "error"
});
loadingInstance.close();
if (arg.success) {
fileList.value = [];
quality.value = 50;
targetDir.value = null;
}
});
});
return {
targetDir,
upload,
quality,
marks,
fileList,
maxFileNum,
handleExceed,
handleChangeFile,
handleSubmit
};
}
};
</script>
немного стилизовано...
Теперь нам нужно ответить на сообщение, отправленное страницей в основном процессе.Общение основного процесса использует ipcMain: ipcMain.on(): получать сообщения, отправленные страницей
// background.js
// 图片压缩:接收 页面发来的消息,arg 为消息参数
ipcMain.on('compress-image', async (event, arg) => {
// 图片压缩
const status = await imageCompress(arg)
// 发送结果给页面
BrowerWindow.webContents.send('compress-status', status)
})
Далее запускаем логику сжатия изображений, utils/utils.js — это инкапсуляция некоторых общих методов, а utils/compress-electron.js — логика сжатия изображений:
// utils.js
import fs from 'fs'
// 创建目录,返回创建目录的结果
const mkdir = (path) => {
return new Promise((resolve, reject) => {
if (fs.existsSync( path )) {
resolve(true)
return
}
fs.mkdir(path, (error) => {
if (error) {
reject(false)
} else {
resolve(true)
}
})
})
}
export {
mkdir,
}
// compress-electron.js
import { nativeImage } from 'electron'
import path from 'path'
import fs from 'fs'
import { mkdir } from './utils'
const imageCompress = (input, quality) => {
quality = quality || 50
const image = nativeImage.createFromPath(input);
const res = image.resize({
// 图片压缩质量,可选值:better || good || best
quality: 'best'
})
console.log(res)
// const imageData = res.toPNG()
// jpg 压缩 图片质量设置
const imageData = res.toJPEG(quality)
return imageData;
}
export default async (options) => {
// 创建保存图片目录,失败的话退出
const createDir = await mkdir(options.targetDir)
if (!createDir) return {
success: false,
msg: '创建图片保存目录失败!'
}
try {
options.fileList.map((item) => {
const dirParse = path.parse(item)
const data = imageCompress(item, options.quality)
const targetDir = `${options.targetDir}${path.sep}${dirParse.name}${dirParse.ext}`
fs.writeFileSync(targetDir,data)
})
return {
success: true,
msg: `图片压缩成功,保存在 ${options.targetDir} 目录中`
}
} catch (err) {
console.log(err, 'err')
return {
success: false,
msg: `图片压缩失败!`,
reason: err
}
}
}
Наконец, введите сжатие-электрон.js в файле электронной записи background.js и скройте меню вверху, полный код background.js:
'use strict'
import { app, protocol, BrowserWindow, ipcMain, Menu } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import imageCompress from './utils/compress-electron.js'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
let BrowerWindow = null
async function createWindow() {
// Create the browser window.
BrowerWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
webSecurity: false,// 取消跨域限制
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await BrowerWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) BrowerWindow.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
BrowerWindow.loadURL('app://./index.html')
}
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
// 图片压缩:接收 页面发来的消息,arg 为消息参数
ipcMain.on('compress-image', async (event, arg) => {
const status = await imageCompress(arg)
console.log('compress-status')
BrowerWindow.webContents.send('compress-status', status)
})
Изображение эффекта:
Пакет
Выполнение заказа
npm run electron:build
Разные компьютеры имеют разную скорость упаковки.Через несколько минут вы можете увидеть файл .exe в папке dist_electron в каталоге проекта.Это пакет, который мы запаковали (система mac тоже запакована таким же образом, просто для генерации суффикс файла отличается), дважды щелкните, чтобы запустить файл .exe, ниже показана демонстрация эффекта:
Больше возможностей
Нативное изображение Electron также может выполнять преобразование изображений (png в jpg, jpg в png), масштабирование и обрезку изображения, которые можно найти вофициальная документацияПросмотрите API и инкапсулируйте общедоступные методы.Визуализация, которую я написал, выглядит следующим образом:
наконец
Если у вас есть ошибки в тексте, поправьте меня ~~
Наконец, спасибо за вашу помощь и руководство~~