小程序图片加载优化方案
一、背景背景小程序在加载的时候容易出现图片加载缓慢的问题项目图片使用现状分析1. 图片类型与来源类型来源处理方式静态资源图片baseImgUrl 相对路径服务器静态资源OSS图片后端返回的阿里云OSS地址已添加processOssImage自动转webp服务器图片BASE_FILEURL 文件名editFormatFileUrl方法拼接二、性能瓶颈识别1. 主要问题问题影响现状修复后DNS解析耗时100-200ms延迟图片域名与页面域名不一致考虑费用上级决策静态资源无CDN服务器带宽瓶颈baseImgUrl直接指向源站考虑费用上级决策无HTTP缓存策略重复下载依赖浏览器默认缓存不适用我们的项目我们需要实时更新首屏图片无预加载白屏时间长关键图片未优先加载首页图片可预先加载但是我们的首页是后台配置图片无法写固定值提前加载瀑布流图片无懒加载并发请求过多部分页面未使用u-lazy-load组件全部开启懒加载oss图片webp格式100-200ms延迟为增加webp后缀所有oss图片已增加webp后缀图片尺寸未适配浪费带宽未根据设备像素比裁剪已修改为读取机型适配图片可能存在的问题没有全局懒加载 大部分 u-image 组件没有启用 lazy-load 属性缺少图片尺寸控制 没有使用OSS图片处理参数如缩略图、压缩重复加载 列表滚动时可能重复加载相同图片首屏加载过多 页面初始化时加载所有可见图片缺少缓存策略 没有利用小程序图片缓存机制大图直接加载 原图直接展示没有渐进式加载- 网络层 OSS跨域请求、HTTPS握手、DNS解析- 图片体积 原图加载没有压缩或裁剪- 并发限制 小程序同时请求数量有限10个- 渲染层 大量图片同时解码导致卡顿- 缺少预加载 没有提前加载即将展示的图片当前已实现的优化使用 u-loading slot 实现加载占位u-parse组件支持 lazyLoad 懒加载图片格式化处理统一封装u-image组件默认已开启懒加载二、调研方案方案一 资源存储策略最核心痛点小程序代码包限制在 2MB过多的本地图片会挤占体积并减慢首屏解析速度。CDN加速所有的业务图片、大图、banner 必须上传至 OSS/COS 等云存储利用CDN进行分发提升加载速度。开启CDN的费用与影响是否收费CDN是独立计费的。流量费开启后产生的下行流量按 CDN 资费计算通常比 OSS 直接外网流出的流量费更便宜。回源费当 CDN 节点没缓存时会去 OSS 取数据这会产生 OSS 的回源流量费。结论对于图片较多、访问量大的小程序CDNOSS的组合通常比单纯用 OSS 更省钱因为 CDN 流量单价更低且有缓存机制。有什么影响正面影响加载速度从“秒级”提升到“毫秒级”减轻 OSS 服务器压力。负面影响缓存同步如果你替换了 OSS 上的某张图但文件名没变CDN 节点可能还缓存着旧图用户看到的还是旧的。这时需要手动在后台“刷新 URL”。预期效果开启后图片加载可缩短至 200ms 左右显著提升用户体验且 CDN 流量单价低于 OSS 外网流量可降低运营成本阿里云 CDN 计费调研报告核心计费模式按流量计费最常用CDN 主要是为了替代 OSS 直接流出流量。对比发现开启 CDN 后单价通常更低。计费项单价 (参考)说明OSS 外网流出流量约 0.50元/GB目前小程序直接访问 OSS 产生的费用CDN 下行流量约 0.24元/GB开启 CDN 后用户访问产生的费用节省幅度约 50%开启 CDN 反而能节省一半的流量费注阿里云经常有流量包促销例如 1TB/1年的流量包可能只需 100-200 元合0.1元/GB左右成本更低。回源流量费新增费用当 CDN 节点上没有缓存某张图片时它会去 OSS 下载这叫“回源”。计费约0.15元/GB。影响只有第一次访问或缓存过期时产生。一旦缓存命中之后成千上万次访问都不再产生此费用。静态资源预热可选如果图片更新非常频繁可能涉及刷新缓存的接口调用费但对于普通小程序图片固定这部分费用通常在免费额度内。调研结果方案一考虑到费用问题不做决策上述内容向上反馈由管理层做决策方案二阿里云 OSS 的域名管理里开启 HTTPS 证书并勾选HTTP/2选项1. 开启 HTTP/2效果解决图片排队等待问题。原理HTTP/1.1 下浏览器对同一个域名同时只能建立 6-8 个连接图片多了就会排队。HTTP/2 支持多路复用几十张图可以同时发送请求。操作即使没开 CDN在阿里云 OSS 的域名管理里通常也可以免费开启 HTTPS 证书并勾选HTTP/2选项。2.阿里云OSS开启 HTTP/2 具体步骤登录控制台登录 阿里云管理控制台。进入存储桶在左侧菜单栏点击Bucket 列表找到存放图片资源的那个 Bucket存储空间。进入域名管理在左侧导航栏中选择传输管理-域名管理。找到目标域名在域名列表中找到自定义域名配置证书/HTTPS点击该域名右侧的证书托管或配置。关键点必须确保“状态”为已开启HTTPS。如果没开启需要先上传证书。开启 HTTP/2在 HTTPS 配置界面中找到HTTP/2 设置选项。将开关切换为开启状态。保存生效点击确定或保存。配置通常在1-5 分钟内全网生效3.特别注意避坑指南必须是HTTPSHTTP/2 协议强制要求在加密连接HTTPS下运行。如果你们目前是 HTTP 访问开启后也不会生效 [1, 2]。浏览器兼容性现代浏览器和微信小程序底层均完美支持 HTTP/2。如果用户手机系统版本极低会自动降级回 HTTP/1.1不会影响访问 [2]。4.如何验证是否成功配置完成后回到微信开发者工具清空缓存刷新页面。在Network面板找到图片请求。查看Protocol列如果显示为h2说明已经成功开启现状调研结果该方案二基于方案1需要开启cdn才能修改为http/2方案目前未开启cdn方案无效方案三采用 “前端动态资源拦截优化” 策略1、 核心原理利用阿里云OSS免费自带的“图片处理Image Processing”功能在代码中通过“全局混入”强制将所有图片转为极小的WebP格式。阿里云 OSS 支持在 URL 后直接拼接处理指令。对于小程序我们最需要的组合是format,webp将图片转为 WebP 格式体积减小约 70%。resize,w_300按需缩放。如果是一个 100px 的头像加载 2000px 的原图就是浪费带宽。quality,q_75画质压缩。75% 是人眼几乎看不出区别、但体积缩减明显的平衡点。兼容性需要对 GIF、视频、PDF 等非图片资源做自动过滤。预期效果在不增加 CDN 费用的情况下全站图片加载速度提升 200%-300%流量成本降低 60%。降低质量 q_60 或 q_50调整尺寸 w_800 或 w_640移除压缩 image/format,webp?x-oss-processimage/resize,w_200,h_200,m_fill // 缩略图?x-oss-processimage/quality,q_80 // 质量压缩?x-oss-processimage/format,jpg // 格式转换?x-oss-processimage/interlace,1 // 渐进显示调研结果已经按照oss图片增加webp处理增加上了后缀图片有明显变化再次优化图片尺寸自适应响应式图片问题 : 当前OSS处理参数固定为 w_1080 未根据设备屏幕适配优化措施 : 根据设备像素比(DPR)动态调整图片尺寸优化前优化后代码思路直接在u-image组件接收的src直接对src进行处理以下是优化过后的u-image组件可以参考oss增加webp的逻辑template view classu-image taponClick :style[wrapStyle, backgroundStyle] image v-if!isError :srcprocessedSrc || src :modemode erroronErrorHandler loadonLoadHandler :lazy-loadlazyLoad classu-image__image :show-menu-by-longpressshowMenuByLongpress :style{ borderRadius: shape circle ? 50% : $u.addUnit(borderRadius) } /image view v-ifshowLoading loading classu-image__loading :style{ borderRadius: shape circle ? 50% : $u.addUnit(borderRadius), backgroundColor: this.bgColor } slot v-if$slots.loading nameloading / u-icon v-else :nameloadingIcon :widthwidth :heightheight/u-icon /view view v-ifshowError isError !loading classu-image__error :style{ borderRadius: shape circle ? 50% : $u.addUnit(borderRadius) } slot v-if$slots.error nameerror / u-icon v-else :nameerrorIcon :widthwidth :heightheight/u-icon /view /view /template script /** * Image 图片 * description 此组件为uni-app的image组件的加强版在继承了原有功能外还支持淡入动画、加载中、加载失败提示、圆角值和形状等。 * tutorial https://uviewui.com/components/image.html * property {String} src 图片地址 * property {String} mode 裁剪模式见官网说明 * property {String | Number} width 宽度单位任意如果为数值则为rpx单位默认100% * property {String | Number} height 高度单位任意如果为数值则为rpx单位默认 auto * property {String} shape 图片形状circle-圆形square-方形默认square * property {String | Number} border-radius 圆角值单位任意如果为数值则为rpx单位默认 0 * property {Boolean} lazy-load 是否懒加载仅微信小程序、App、百度小程序、字节跳动小程序有效默认 true * property {Boolean} show-menu-by-longpress 是否开启长按图片显示识别小程序码菜单仅微信小程序有效默认 false * property {String} loading-icon 加载中的图标或者小图片默认 photo * property {String} error-icon 加载失败的图标或者小图片默认 error-circle * property {Boolean} show-loading 是否显示加载中的图标或者自定义的slot默认 true * property {Boolean} show-error 是否显示加载错误的图标或者自定义的slot默认 true * property {Boolean} fade 是否需要淡入效果默认 true * property {String Number} width 传入图片路径时图片的宽度 * property {String Number} height 传入图片路径时图片的高度 * property {Boolean} webp 只支持网络资源只对微信小程序有效默认 false * property {String | Number} duration 搭配fade参数的过渡时间单位ms默认 500 * event {Function} click 点击图片时触发 * event {Function} error 图片加载失败时触发 * event {Function} load 图片加载成功时触发 * example u-image width100% height300rpx :srcsrc/u-image */ export default { name: u-image, props: { // 图片地址 src: { type: String, default: }, // 裁剪模式 mode: { type: String, default: aspectFill }, // 宽度单位任意 width: { type: [String, Number], default: 100% }, // 高度单位任意 height: { type: [String, Number], default: auto }, // 图片形状circle-圆形square-方形 shape: { type: String, default: square }, // 圆角单位任意 borderRadius: { type: [String, Number], default: 0 }, // 是否懒加载微信小程序、App、百度小程序、字节跳动小程序 lazyLoad: { type: Boolean, default: true }, // 开启长按图片显示识别微信小程序码菜单 showMenuByLongpress: { type: Boolean, default: true }, // 加载中的图标或者小图片 loadingIcon: { type: String, default: photo }, // 加载失败的图标或者小图片 errorIcon: { type: String, default: error-circle }, // 是否显示加载中的图标或者自定义的slot showLoading: { type: Boolean, default: true }, // 是否显示加载错误的图标或者自定义的slot showError: { type: Boolean, default: true }, // 是否需要淡入效果 fade: { type: Boolean, default: true }, // 只支持网络资源只对微信小程序有效 webp: { type: Boolean, default: false }, // 过渡时间单位ms duration: { type: [String, Number], default: 500 }, // 背景颜色用于深色页面加载图片时为了和背景色融合 bgColor: { type: String, default: #f3f4f6 } }, data() { return { // 图片是否加载错误如果是则显示错误占位图 isError: false, // 初始化组件时默认为加载中状态 loading: true, // 不透明度为了实现淡入淡出的效果 opacity: 1, // 过渡时间因为props的值无法修改故需要一个中间值 durationTime: this.duration, // 图片加载完成时去掉背景颜色因为如果是png图片就会显示灰色的背景 backgroundStyle: {}, // 处理后的图片地址添加webp后缀 processedSrc: }; }, watch: { src: { immediate: true, handler (n) { if(!n) { // 如果传入null或者或者false或者undefined标记为错误状态 this.isError true; this.loading false; this.processedSrc ; } else { this.isError false; // 处理OSS图片自动添加webp后缀 this.processedSrc this.processOssImage(n); } } } }, computed: { wrapStyle() { let style {}; // 通过调用addUnit()方法如果有单位如百分比px单位等直接返回如果是纯粹的数值则加上rpx单位 style.width this.$u.addUnit(this.width); style.height this.$u.addUnit(this.height); // 如果是配置了圆形设置50%的圆角否则按照默认的配置值 style.borderRadius this.shape circle ? 50% : this.$u.addUnit(this.borderRadius); // 如果设置圆角必须要有hidden否则可能圆角无效 style.overflow this.borderRadius 0 ? hidden : visible; if (this.fade) { style.opacity this.opacity; style.transition opacity ${Number(this.durationTime) / 1000}s ease-in-out; } return style; } }, methods: { // 点击图片 onClick() { this.$emit(click); }, // 图片加载失败 onErrorHandler(err) { this.loading false; this.isError true; this.$emit(error, err); }, // 图片加载完成标记loading结束 onLoadHandler() { this.loading false; this.isError false; this.$emit(load); // 如果不需要动画效果就不执行下方代码同时移除加载时的背景颜色 // 否则无需fade效果时png图片依然能看到下方的背景色 if (!this.fade) return this.removeBgColor(); // 原来opacity为1(不透明是为了显示占位图)改成0(透明意味着该元素显示的是背景颜色默认的灰色)再改成1是为了获得过渡效果 this.opacity 0; // 这里设置为0是为了图片展示到背景全透明这个过程时间为0延时之后延时之后重新设置为duration是为了获得背景透明(灰色) // 到图片展示的过程中的淡入效果 this.durationTime 0; // 延时50ms否则在浏览器H5过渡效果无效 setTimeout(() { this.durationTime this.duration; this.opacity 1; setTimeout(() { this.removeBgColor(); }, this.durationTime); }, 50); }, // 移除图片的背景色 removeBgColor() { // 淡入动画过渡完成后将背景设置为透明色否则png图片会看到灰色的背景 this.backgroundStyle { backgroundColor: transparent }; }, // 处理OSS图片自动添加webp格式后缀并根据尺寸自适应压缩 processOssImage(url) { if (!url || typeof url ! string) { return url; } // 判断是否为OSS图片包含oss关键词或阿里云OSS域名特征 const isOssImage url.includes(oss) || url.includes(aliyuncs.com) || url.includes(oss-cn-); if (!isOssImage) { return url; } // 如果已经包含x-oss-process参数追加format,webp if (url.includes(x-oss-process)) { // 如果已经包含webp格式不再处理 if (url.includes(format,webp)) { return url; } // 追加webp格式转换 return url /format,webp; } // 根据组件尺寸计算合适的图片宽度 const targetWidth this.getTargetWidth(); // 根据图片尺寸和使用场景选择质量参数 const quality this.getQualityBySize(targetWidth); // OSS图片处理参数自适应尺寸、转换为webp格式、动态质量 const OSS_PROCESS_PARAMS image/resize,w_${targetWidth},m_lfit/format,webp/quality,q_${quality}; // 如果没有x-oss-process参数添加完整的处理参数 const separator url.includes(?) ? : ?; return url separator x-oss-process OSS_PROCESS_PARAMS; }, // 根据组件宽度计算目标图片宽度考虑设备像素比 getTargetWidth() { try { // 获取设备信息 const systemInfo uni.getSystemInfoSync(); const dpr systemInfo.pixelRatio || 1; const screenWidth systemInfo.windowWidth || 375; // 解析组件宽度 let componentWidth this.parseWidth(this.width, screenWidth); // 根据设备像素比计算实际需要的图片宽度 let targetWidth Math.ceil(componentWidth * dpr); // 限制最大宽度避免过大图片 const MAX_WIDTH 1080; const MIN_WIDTH 100; if (targetWidth MAX_WIDTH) { targetWidth MAX_WIDTH; } else if (targetWidth MIN_WIDTH) { targetWidth MIN_WIDTH; } // 按50的倍数取整增加缓存命中率 return Math.ceil(targetWidth / 50) * 50; } catch (e) { // 异常情况下返回默认值 return 800; } }, // 解析组件宽度返回px数值 parseWidth(width, screenWidth) { if (typeof width number) { // 数值类型认为是rpx转换为px return width / 2; } if (typeof width string) { // 处理百分比 if (width.includes(%)) { const percent parseFloat(width) / 100; return screenWidth * percent; } // 处理rpx if (width.includes(rpx)) { return parseFloat(width) / 2; } // 处理px if (width.includes(px)) { return parseFloat(width); } // 纯数字字符串 if (!isNaN(parseFloat(width))) { return parseFloat(width) / 2; } } // 默认返回屏幕宽度 return screenWidth; }, // 根据图片尺寸选择质量参数 getQualityBySize(targetWidth) { // 小图标/头像高压缩率质量60 if (targetWidth 150) { return 60; } // 中等尺寸图片质量70 if (targetWidth 400) { return 70; } // 大图质量75平衡清晰度与体积 if (targetWidth 800) { return 75; } // 超大图质量80保证清晰度 return 80; } } }; /script style scoped langscss import ../../libs/css/style.components.scss; .u-image { position: relative; transition: opacity 0.5s ease-in-out; __image { width: 100%; height: 100%; } __loading, __error { position: absolute; top: 0; left: 0; width: 100%; height: 100%; include vue-flex; align-items: center; justify-content: center; background-color: $u-bg-color; color: $u-tips-color; font-size: 46rpx; } } /style方案四后端OSS资源元数据强缓存优化技术实现在后端调用阿里云 OSS SDK 上传图片的代码逻辑中通过设置ObjectMetadata对象元数据统一为资源注入以下 HTTP 响应头配置参数Cache-Control: max-age31536000实施方式在执行putObject上传操作时全局配置metadata.setCacheControl(max-age31536000)深度解析什么是max-age31536000定义Cache-Control是 HTTP 协议中控制缓存的核心指令。max-age代表资源在客户端用户手机中被视为“新鲜”的最大时间单位为秒。数值换算31536000秒 3600秒 × 24小时 × 365天 1 年。运行机制当小程序首次下载图片后手机浏览器会将该图片存入本地磁盘或内存。在未来的一年内只要图片 URL 不变手机将直接从本地读取不再向阿里云服务器发送任何网络请求。为什么需要这样做核心痛点解决消除网络排队延迟在 HTTP/1.1 协议下浏览器对同一域名的并发请求有限制。如果不设缓存每次打开页面图片都要“排队下载”。开启强缓存后图片加载跳过了网络阶段彻底解决“1-2秒才出图”的尴尬。解决重复渲染闪烁用户在切换页面或二次进入小程序时由于资源已在本地图片会随页面同步“瞬时弹出”消除先白屏、后出图的视觉闪烁感。. 方案优势与商业价值极致的加载性能0ms 响应 二次访问时图片的加载耗时将从“秒级”直接降至0毫秒显示为from memory cache或from disk cache。大幅降低运营成本省钱 阿里云 OSS 是按下行流量计费的。配置强缓存后大量重复的图片访问不再产生外网流出流量。根据行业测算此举可为公司节省30% - 60%的 OSS 流量费用。提高系统稳定性 极大降低了高并发时期 OSS 服务器的并发压力确保核心业务接口如登录、支付在高峰期拥有更多的带宽资源。风险控制如果图片需要更新怎么办文件名版本号化对于需要更新的图片如活动 Banner建议在上传时修改文件名或在前端 URL 后拼接版本号如?v20260311。结论由于 URL 变动会被视为新资源手机会自动重新下载并缓存。这确保了“静态图标永久缓存动态资源受控更新”的完美平衡。配置max-age3600(1小时)优势时效性极高。体验用户在一个小时内点击小程序是秒开的。代价相比 1 天会产生更多的 OSS 流量请求。调研结果不建议使用虽然0ms秒开图片但是因为365天意味着图片一直未更新运营侧上传的图片无法实时更新如果更新为1小时配置可以看情况考虑但是要基于我们的运营经常就该患教或者其他图片相关配置决定方案五: HTTP缓存头优化问题: 静态资源缓存策略不明确优化措施: 在OSS/服务器端配置缓存头nginx# Nginx配置示例location ~* \.(png|jpg|jpeg|gif|webp|svg)$ {expires 30d; # 图片缓存30天add_header Cache-Control public, immutable;add_header Vary Accept-Encoding;}# 带hash的文件长期缓存location ~* \.[a-f0-9]{8,}\.(png|jpg|jpeg|gif|webp)$ {expires 1y;add_header Cache-Control public, immutable;}调研结果缓存三十天的方案和上面的缓存也差不多无法实时更新方案六: 首屏关键图片预加载使用首次获取低质量压缩图获取到接口再展示清晰图问题: 首屏图片如轮播图、logo与页面同时加载造成白屏优化措施: 在App.vue中提前预加载关键图片主要处理大尺寸的图片封面图、头像等小图标20x20, 32x32, 36x36等不需要渐进式加载反而会更久首页IM头像预加载组件template view classprogressive-image-wrapper :style{ width: imgWidth, height: imgHeight, borderRadius: imgRadius } !-- 模糊缩略图 -- image v-ifshowThumb thumbUrl classprogressive-image thumb :srcthumbUrl :modemode :style{ width: imgWidth, height: imgHeight, borderRadius: imgRadius, filter: blur(10px), transform: scale(1.1) } / !-- 骨架屏 -- view v-ifloading !showThumb classprogressive-skeleton :style{ width: imgWidth, height: imgHeight, borderRadius: imgRadius, backgroundColor: bgColor } view classprogressive-shimmer/view /view !-- 高清原图 -- image classprogressive-image original :srcoriginalUrl :modemode :style{ width: imgWidth, height: imgHeight, borderRadius: imgRadius, opacity: originalLoaded ? 1 : 0, transition: opacity ${fadeDuration}ms ease-in-out } loadonOriginalLoad erroronOriginalError / /view /template script /** * ProgressiveImage 渐进式图片加载组件 * description 先加载模糊缩略图再加载高清原图实现渐进式加载效果 * property {String} src 原图URL * property {String} thumb 缩略图URL不传则自动生成 property {String | Number} width 宽度默认100% * property {String | Number} height 高度默认200rpx * property {String | Number} borderRadius 圆角默认0 * property {String} mode 图片裁剪模式默认aspectFill * property {String} bgColor 骨架屏背景色默认#f0f0f0 * property {Number} fadeDuration 淡入动画时长单位ms默认300 * property {Number} thumbQuality 缩略图质量 1-100默认30 * property {Number} thumbWidth 缩略图宽度默认200 * example * progressive-image * srchttps://example.com/image.jpg * width300rpx * height200rpx * borderRadius12rpx * /progressive-image */ export default { name: ProgressiveImage, props: { src: { type: String, default: }, thumb: { type: String, default: }, width: { type: [String, Number], default: 100% }, height: { type: [String, Number], default: 200rpx }, borderRadius: { type: [String, Number], default: 0 }, mode: { type: String, default: aspectFill }, bgColor: { type: String, default: #f0f0f0 }, fadeDuration: { type: Number, default: 300 }, thumbQuality: { type: Number, default: 30 }, thumbWidth: { type: Number, default: 200 } }, data() { return { loading: true, originalLoaded: false, thumbLoaded: false, loadError: false } }, computed: { imgWidth() { return this.$u.addUnit(this.width); }, imgHeight() { return this.$u.addUnit(this.height); }, imgRadius() { if (this.borderRadius circle || this.borderRadius 50%) { return 50%; } return this.$u.addUnit(this.borderRadius); }, // 生成缩略图URL thumbUrl() { if (this.thumb) return this.thumb; if (!this.src) return ; // 如果是OSS图片添加压缩参数 if (this.src.includes(aliyuncs.com) || this.src.includes(oss-)) { const params [ resize,w_${this.thumbWidth}, format,webp, quality,q_${this.thumbQuality} ]; return ${this.src}?x-oss-processimage/${params.join(/)}; } return this.src; }, originalUrl() { return this.src; }, showThumb() { return this.thumbUrl !this.originalLoaded !this.loadError; } }, watch: { src: { immediate: true, handler(newVal) { if (newVal) { this.loading true; this.originalLoaded false; this.loadError false; this.preloadThumb(); } } } }, methods: { // 预加载缩略图 preloadThumb() { if (!this.thumbUrl) return; uni.downloadFile({ url: this.thumbUrl, success: () { this.thumbLoaded true; }, fail: () { // 缩略图加载失败直接显示骨架屏等待原图 this.thumbLoaded false; } }); }, // 原图加载完成 onOriginalLoad() { this.originalLoaded true; this.loading false; this.$emit(load); }, // 原图加载失败 onOriginalError() { this.loadError true; this.loading false; this.$emit(error); } } } /script style scoped langscss .progressive-image-wrapper { position: relative; overflow: hidden; background-color: #f5f5f5; } .progressive-image { position: absolute; top: 0; left: 0; will-change: opacity, transform; .thumb { z-index: 1; } .original { z-index: 2; } } .progressive-skeleton { position: absolute; top: 0; left: 0; z-index: 0; overflow: hidden; } .progressive-shimmer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100% ); animation: shimmer 1.5s infinite; } keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } /style方案7: 骨架屏优化已部分实现现状: 已有Skeleton组件但未全面应用优化建议: 在图片加载区域使用骨架屏占位太快截图不到闪烁骨架屏组件template !-- 图片骨架屏占位组件 -- view classimage-skeleton-wrapper :style{ width: skeletonWidth, height: skeletonHeight, borderRadius: skeletonRadius } view v-ifloading classimage-skeleton :style{ width: 100%, height: 100%, borderRadius: skeletonRadius, backgroundColor: bgColor } view classimage-skeleton__shimmer/view /view slot v-else/slot /view /template script /** * ImageSkeleton 图片骨架屏占位组件 * description 在图片加载前显示骨架屏占位提升用户体验 * property {String | Number} width 宽度支持rpx、px、%默认100% * property {String | Number} height 高度支持rpx、px默认200rpx * property {String | Number} borderRadius 圆角默认0 * property {Boolean} loading 是否显示骨架屏默认true * property {String} bgColor 骨架屏背景色默认#f0f0f0 * example * image-skeleton width200rpx height200rpx borderRadius50% :loading!imageLoaded * u-image width200 height200 :srcimageUrl loadimageLoaded true/u-image * /image-skeleton */ export default { name: ImageSkeleton, props: { width: { type: [String, Number], default: 100% }, height: { type: [String, Number], default: 200rpx }, borderRadius: { type: [String, Number], default: 0 }, loading: { type: Boolean, default: true }, bgColor: { type: String, default: #f0f0f0 } }, computed: { skeletonWidth() { return this.$u.addUnit(this.width); }, skeletonHeight() { return this.$u.addUnit(this.height); }, skeletonRadius() { if (this.borderRadius circle || this.borderRadius 50%) { return 50%; } return this.$u.addUnit(this.borderRadius); } } } /script style scoped langscss .image-skeleton-wrapper { position: relative; overflow: hidden; } .image-skeleton { position: relative; overflow: hidden; __shimmer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100% ); animation: shimmer 1.5s infinite; } } keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } /style方案5: 静态资源本地缓存策略问题: 静态图标每次都需要网络请求优化措施: 将常用小图标转为Base64或使用小程序本地资源思考我们的静态资源都是放在服务器上通过服务器路径拼接如果常用小图标改为本地资源是否会影响小程序包体积大小
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2411622.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!