客户端优化(小程序+后台) | 【图片优化】(二)
前言
一名正在自由职业的程序员的独立开发之路 每天100元的图片流量费用 逼得我不得不做图片优化 目前已经全部优化完成 希望我的经历能给你带来帮助 这是图片优化的第二篇
说明
我的项目主要客户端主要包括 小程序和后台管理
图片优化系列文章
- 每天100元的图片费用,就是我主动优化图片的动力 | 【图片优化】(一)
- 客户端优化(小程序+后台) | 【图片优化】(二)
- node开发一个图片服务器(koa) | 【图片优化】(三)
- 如何检测图片服务的性能 | 【图片优化】(四)(番外篇)
小程序优化
1.不怎么变化的图片全部存放到本地
很多图片都是通过网络下发的,这样确实方便灵活。
但是目前业务已经稳定,不会有太大的变化
像图标、banner图,这些基本不会太有变化,那么就直接放到小程序里面,直接加载本地资源
当然小程序的包大小也要控制
如果压缩后的体积大于2M,那么会不给上传,再做一定的取舍
当小程序上传包太大,点击可以看得到每一块模块的大小,占的比例大小,帮助你分析
另外我也采取了分包的策略
2.图片全部都通过工具优化一遍
这个可以通过网上的工具,也可以通过自己代码去压缩
当然压缩完之后,至少要保证不至于太糊
经过摸索,我这边基本上 乘以 0.3 的系数是最好的
3.网络图片加载完,下载到本地
小程序这边我做一个图片加载的组件
在得到图片的URL
的时候,直接去本地存储中去找,之前有没有下载过
如果下载过了,直接拿上次下载的用
如果没有下载过,那么直接下载下来,用URL
作为Key
直接上代码了
# 组件的html
<image class="ff-class" src='{{path}}' lazy-load="true" mode="{{mode}}" bindload="{{bindload}}" binderror="{{binderror}}" />
#组件的js
const app = getApp()
const fileCache = require('../../utils/fileCache')
console.log('fileCache ===',fileCache)
Component({
externalClasses: ['ff-class'],
/**
* 组件的属性列表
*/
properties: {
mode: {
type: String,
default: '',
},
src:{
type:String,
default: '',
observer:function(newVal){
this.initUrl()
}
}
},
/**
* 组件的初始数据
*/
data: {
ffmode:'scaleToFill',
path:'',
},
lifetimes: {
attached() {
this.initUrl()
},
},
/**
* 组件的方法列表
*/
methods: {
initUrl(){
// console.log('initUrl ======')
let {src,mode} = this.properties
console.log('mode ===',mode)
if(!/^https/.test(src)) src = src.replace('http','https')
let path = fileCache.getStorageImage(src)
this.setData({path})
if(mode) this.setData({ffmode:mode})
console.log('path ====',path)
},
binderror(error){
console.log('error ====',error)
},
bindload(res){
console.log('加载图片成功 ====',error)
}
}
})
# 下载的逻辑
# fileCache.js
const fileSystem = wx.getFileSystemManager()
const getStorageImage = (web_image) => {
let webImages = wx.getStorageSync('webImages') || []
let webImage = webImages.find(y => y.web_path === web_image)
if (webImage) {
try {
fileSystem.accessSync(webImage.local_path)
return webImage.local_path
} catch(e) {
let webImageIdx = webImages.findIndex(y => y.web_path === web_image)
webImages.splice(webImageIdx, 1)
wx.setStorageSync('webImages', webImages)
}
} else {
wx.downloadFile({
url: web_image,
success : res =>{
if (res.statusCode === 200) {
let filePath = res.tempFilePath
let webImageStorage = wx.getStorageSync('webImages') || []
let storage = {
web_path: web_image,
local_path: filePath,
last_time: Date.parse(new Date()),
}
webImageStorage.push(storage)
wx.setStorageSync('webImages', webImageStorage)
}
},
fail: err=>{
console.log('downLoad 报错了',err)
}
})
}
return web_image
}
module.exports = {
getStorageImage
}
管理后台优化(Vue)
管理后台主要功能是上传图片,所以压缩图片才是最大的优化
压缩图片
压缩图片的方法为getZipFile
通过getUrlByFile
方法获取图片的 URL
-
如果文件的大小乘以 0.3 之后还是大于 60KB,那么就调用
startZipToSize
这个方法是把图片压缩到固定的大小内 里面主要通过二分法不断的去压缩图片,最后也有可能不能达到60KB -
如果文件大小乘以 0.3小于60KB,那么就调用
startZipQuality
,这个方法就是按照 0.3 的系数去压缩图片
// v2.0.8 压缩图片
const getZipFile = async ({file,url})=>{
// 通过file 得到url
// 判断如果 file 的size * 0.3 > 60
let {original_url } = await getUrlByFile({file})
if((file/1024)*0.3 >= 60){
const {zip_file} = await startZipToSize({original_url,fileName:file.name})
if(!zip_file) return alert('getZipFile = startZipToSize =获取zip_file 失败')
return zip_file
}else{
const {zip_file} = await startZipQuality({original_url,fileName:file.name})
if(!zip_file) return alert('getZipFile = startZipQuality =获取zip_file 失败')
return zip_file
}
}
# 通过文件获取URL
const getUrlByFile = ({file})=>{
if(!file)return alert('getUrlByFile 中file参数不存在')
if(!window.FileReader) return alert('请更换浏览器,此浏览器不支持 FileReader')
return new Promise((resolve,reject)=>{
let reader = new FileReader()
reader.onload = (event)=>{
let original_url = event.target.result
console.log('通过 file 后的 image 的url 的地址为 ...',original_url)
resolve({original_url})
// original_file = e.target.files[0]
}
reader.readAsDataURL(file)
})
}
/**
* 将图片压缩到 一定的比例之内(默认是0.3)
* @param {*} param0
* @returns
*/
const startZipQuality = ({quality = 0.3,original_url='',fileName=''})=>{
if(/^http/.test(original_url))
if(!fileName) return alert('startZipQuality == fileName 不存在')
console.log('fileName ===',fileName)
return new Promise((reslove,reject)=>{
var image = new Image() // 创建 img 元素
image.onload = async()=>{
var canvasUrl,miniFile;
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d').drawImage(image,0,0,image.width,image.height) // 绘制canvas
canvasUrl = canvas.toDataURL('image/jpeg',quality )
const buffer = atob(canvasUrl.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while(length--){
bufferArray[length] = buffer.charCodeAt(length)
}
miniFile = new File([bufferArray], fileName, { type: 'image/jpeg' })
let zip_url = await getUrlByFile({file:miniFile})
let zip_file = miniFile
reslove({zip_file,zip_url,zip_img:image})
}
image.setAttribute('crossOrigin', 'anonymous') //关键
image.src = /^http/.test(original_url) ? `${original_url}?time=${new Date().valueOf()}` : original_url
})
}
/**
* 压缩到一定的体积内 默认是60K
* @param {*} param0
*/
const startZipToSize = ({limitSize = 60 ,original_url='',fileName=''})=>{
if(/^http/.test(original_url))
if(!fileName) return alert('startZipToSize == fileName 不存在')
return new Promise((reslove,reject)=>{
var image = new Image()
image.onload = async()=>{
var canvasUrl,miniFile;
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d').drawImage(image,0,0,image.width,image.height) // 绘制canvas
let L = true // 判断是左侧还是右侧 二分法
let quality = 0
let start = 0,end = 1;
let count = 0;
let lastSize = 0;
while(count <= 2){
quality = (start+end)/2
canvasUrl = canvas.toDataURL('image/jpeg',quality )
const buffer = atob(canvasUrl.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while(length--){
bufferArray[length] = buffer.charCodeAt(length)
}
miniFile = new File([bufferArray], fileName, { type: 'image/jpeg' })
if(lastSize === miniFile.size){
console.log(`法压缩到指定大小,最小为${lastSize/1024}KB`)
// alert(`法压缩到指定大小,最小为${lastSize/1024}KB`)
break;
}
lastSize = miniFile.size
if(miniFile.size/1024 > limitSize ){
end = quality
}else{
start = quality
count++
}
}
let url = await getUrlByFile({file:miniFile})
let zip_url = url
let zip_file = miniFile
reslove({zip_file,zip_url,zip_img:image})
}
image.setAttribute('crossOrigin', 'anonymous') //关键
image.src = /^http/.test(original_url) ? `${original_url}?time=${new Date().valueOf()}` : original_url
})
}
上传
由于我已经做了自己的图片服务器,所以此处的删除直接传到我自己的服务器
//zip_file 图片文件 prefix 图片命名的前缀,可以不穿
const submitImg = ({zip_file,prefix=""})=>{
if(!zip_file) return alert('submitImg 中不存在zip_file')
console.log('submitImg === zip_file',zip_file)
return new Promise(async (resolve,reject)=>{
const data = new FormData();
data.set('file', zip_file);
if(prefix) data.set('prefix', prefix);
let requestUrl = /^https/.test(window.location.href) ? 'https://abc/upload' : 'http://abc/upload'
const res = await axios.post(requestUrl, data, {
headers: { 'content-type': 'multipart/form-data' }
});
let backData = res.data
if(backData && backData.code === 200 && backData.data && backData.data.url){
// console.log('-----上传成功',zip_file.name)
resolve({upload_url:backData.data.url})
}else{
// console.log('-----上传失败',zip_file.name)
reject()
}
console.log(res);
})
}
总结
小程序和管理后台优化起来还是比较简单
一个是根据业务来优化,存本地,节省网络请求流量
一个是上传图片做压缩
这是一个【图片优化】系列文章的【第二篇】,正在更新
转载自:https://juejin.cn/post/7242550697521594428