当前位置: 首页 > article >正文

小程序图片加载优化方案

一、背景背景小程序在加载的时候容易出现图片加载缓慢的问题项目图片使用现状分析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或使用小程序本地资源思考我们的静态资源都是放在服务器上通过服务器路径拼接如果常用小图标改为本地资源是否会影响小程序包体积大小

相关文章:

小程序图片加载优化方案

一、背景背景:小程序在加载的时候容易出现图片加载缓慢的问题项目图片使用现状分析1. 图片类型与来源类型来源处理方式静态资源图片baseImgUrl 相对路径服务器静态资源OSS图片后端返回的阿里云OSS地址已添加processOssImage自动转webp服务器图片BASE_FILEURL 文件…...

2026年七大闷声赚钱的AI技能

AI 技能差距:过去 20 年最大的财富风口在这个 AI 席卷全球的时代,我们正在目睹过去二十年来最大的财富分配机会。尽管 AI 的热度居高不下,但一个残酷的现实是:绝大多数人依然完全闲置着这项技术。他们或许听说过,却从未…...

基于电压电流双闭环和模糊PID双环的VIENNA整流器仿真研究

基于电压电流双闭环的vienna整流器的仿真(SVPWM调制) 基于模糊pid双环的vienna整流器仿真 适用于毕业,查重率10%以下 注:仿真页数34ppt随着可再生能源的广泛应用,高效、可靠的能量转换技术成为研究热点。VIENNA整流器作为一种先进…...

实习面经摘录(六)

来自网络,供个人学习,侵删 某滴 先完成再完美1.JVM内存结构也被定义为 运行时数据区,JVM内存结构分为两大类:线程私有区域/线程共享区域3.内存泄漏怎么处理排查常见内存泄漏场景静态集合类:不断add却从不remove。未关闭…...

导师推荐!AI论文写作软件 千笔写作工具 VS PaperRed,专为本科生量身打造!

随着人工智能技术的迅猛迭代与普及,AI辅助写作工具已逐步渗透到高校学术写作场景中,成为本科生完成毕业论文不可或缺的智能助手。越来越多的学生在面对繁重的写作任务时,开始依赖各类AI工具来提升效率、简化流程。然而,市场上AI写…...

实测对比后!倍受青睐的降AIGC软件 —— 千笔AI

在AI技术迅速渗透学术写作领域的当下,越来越多的本科生开始借助AI工具提升论文写作效率。然而,随着查重系统对AI生成内容的识别能力不断提升,如何有效降低AI率和重复率成为许多学生面临的难题。面对市场上五花八门的降AI工具,选择…...

MVI46-AFC串行通讯模块

MVI46-AFC串行通讯模块是一种用于工业自动化系统的数据处理与通信模块,通常安装在SLC系列PLC平台中。该模块主要用于流量计算与串行数据通信,可对来自现场仪表的流量、压力、温度等信号进行计算和处理,并通过串行接口实现与其他控制设备或监控…...

[算法][力扣3]无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。示例 1:输入: s "abcabcbb"输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。示例 2…...

《基于非对称纳什谈判的多微网电能共享运行优化策略》完美复现 仿真平台:MATLAB CPLEX...

《基于非对称纳什谈判的多微网电能共享运行优化策略》完美复现 仿真平台:MATLAB CPLEXMOSEK/IPOPT 主要做的是微网间基于非对称纳什谈判的P2P电能交易共享问题,基于纳什谈判理论建立了多微网电能共享合作运行模型,进而将其分解为微网联盟效益…...

Power BI技巧:度量值整理指南

在Power BI数据建模过程中,随着业务复杂度提升,度量值数量会不断增多。杂乱无章的度量值不仅会增加查找成本,还会影响团队协作效率。 今天就来分享一套度量值整理的最佳实践,帮你打造清晰、高效的模型结构。 度量值与列的核心区别…...

Canal同步完了,怎么验证数据对得上?

比起同步失败,较为棘手的是“看似成功”作为一名 DBA,深夜收到开发的消息:“Canal 同步任务跑完了,准备明天切业务,你帮看看数据对不对得上?”你熟练地登录数据库,准备手工核对几张核心表的数据…...

企微朋友圈自动化发布的技术实现

摘要企业微信朋友圈是企业触达客户的高效渠道,但官方仅支持手动发布,无法规模化运营。本文基于2026年度企微运营工具top3排行榜的技术视角,详解如何利用企微官方API和第三方iPad协议实现朋友圈的自动化创建、定时发布和效果追踪。提供两种技术…...

2026毕业论文AIGC降重权威评测,免费试用盘点!

摘要/前言: 欢迎来到2026年的毕业季。如果你还以为毕业论文的终极BOSS只是“查重率”,那么你可能已经输在了起跑线上。如今,高校的审查系统已经进化为“查重AIGC溯源”的多维审计模式。任何未经深度处理的AI生成内容,都如同在你的…...

2026小程序开发与运营指南:选对服务商,用对会员体系,轻松提升复购

2026小程序开发与运营指南:选对服务商,用对会员体系,轻松提升复购 在移动互联网深度渗透的今天,小程序已成为企业数字化转型的核心载体。2026年中国小程序服务市场规模突破900亿元,用户规模更是高达15亿,无…...

蔚来终于盈利,李斌能开香槟了吗

蔚来的用户和蔚来董事长李斌终于能松一口气了。成立11年,蔚来终于盈利了。3月10日蔚来公布了2025年第四季度及全年财务报告。报告显示,本季度蔚来共计交付12.5万辆车,同比增长超七成,经营利润(Non-GAAP)为1…...

蓝汛-897-添加6击按键事件

蓝汛-897-添加6击按键事件...

IP 溯源技术原理

IP 溯源技术原理 近期,一些网站(如 ip.sy、iptrack.nmqu.com)能够将外网 IP 地址精确到国内具体小区,引发广泛关注。本文从技术角度解析其实现原理,并通过实例数据展示定位精度,重点揭示外网 IP 如何被绑定…...

智慧党建:让线上线下融合,真正激活基层党组织活力

信息化背景下,党建迎来数字化转型关键。智慧党建系统以技术为抓手,打破传统党建时空壁垒与效率瓶颈,破解“管理难覆盖、学习难常态、考核难量化”痛点,为基层党组织注入新动能。智慧党建系统通过整合核心功能,构建“线…...

超简单!直接用封装好的二阶线性自抗扰控制模型

自抗扰控制模型,已封装好,可直接使用 二阶线性自抗扰家人们,今天来给大家分享一下已经封装好、可以直接使用的二阶线性自抗扰控制模型。在控制领域,自抗扰控制可是相当厉害的一种控制策略,它能有效处理系统中的不确定性…...

机房ip原生ip住宅ip性质区别

一、核心概念详解 1. 云服务器(Cloud Server) 不是 IP 类型,是基于云平台的虚拟服务器(如阿里云 ECS、腾讯云 CVM、AWS EC2 等)。本质:通过虚拟化技术,在物理服务器上划分出的独立虚拟主机&…...

亿迈跨境分销商城启航

当全球电商市场规模持续攀升,跨境卖家面临的却是一场关于资金周转与库存管理的双重考验。Statista研究显示,2025年全球电商市场规模预计达4.32万亿美元,面对这片广阔的蓝海,卖家们迫切需要一种既能快速启动、又能控制风险的出海模…...

2026中国停车场管理系统十大标杆供应商榜单——智赋停车,共筑城市出行新生态

随着新能源汽车的快速普及与城市智慧化建设的持续升级,停车场管理系统已彻底摆脱单一收费工具的定位,升级为“硬件平台服务”的综合生态体系。以下十大供应商凭借深厚的技术实力、多元的场景适配能力与良好的市场口碑,成为引领行业高质量发展…...

白色情人节,予你无尽的爱

源码&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>3.14白色情人节 只对你心动<…...

什么是公共DNS地址?

在日常网络使用中&#xff0c;你是否遇到过网页加载缓慢、域名解析失败的情况&#xff1f;这大概率和DNS设置有关。公共DNS地址作为第三方域名解析服务&#xff0c;能绕过运营商默认DNS&#xff0c;带来更稳定、快速的网络体验。很多人对公共DNS地址了解不深&#xff0c;不知道…...

计算机毕业设计源码:Python 携程旅游数据分析大屏系统 Django框架 selenium 爬虫 大数据 大模型 数据分析 agent 机器学习 旅行 出游 出行(建议收藏)✅

1、项目介绍 技术栈 Python作为主要开发语言&#xff0c;MySQL作为数据存储数据库&#xff0c;Django作为后端Web框架&#xff0c;selenium用于携程网旅游数据的爬取采集&#xff0c;HTML用于前端页面展示。 功能模块旅游景点信息采集模块注册登录模块系统数据概况模块…...

工业互联网IOT平台介绍(二):工业协议

工业协议&#xff08;也叫工业通信协议或工控协议&#xff09;是指专门为工业自动化和工业控制系统设计的通信规则和标准。它定义了PLC、传感器、变频器、伺服驱动器、HMI、上位机等各种工业设备之间如何可靠地交换数据&#xff0c;包括数据格式、传输时序、错误检测、主从/生产…...

springboot+vue二手物品交易boot代码--毕业论文

目录后端代码&#xff08;SpringBoot&#xff09;项目结构核心代码示例前端代码&#xff08;Vue&#xff09;项目结构核心代码示例数据库配置&#xff08;application.yml&#xff09;扩展功能建议项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获…...

心电域泛化研究从0入门系列 | 第三篇:数据集+多源域划分+标准评估——域泛化科研的“实验地基”

写在第三篇开篇&#xff1a;数据和实验规则&#xff0c;是域泛化的“立身之本”走完前两篇的基础认知和数据预处理&#xff0c;我们终于要触碰心电域泛化最核心的实操前提&#xff1a;用什么数据做研究、怎么把数据划分成“源域”和“目标域”、跑完模型后怎么证明它有泛化能力…...

DBeaver Ultimate Edtion 26.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具

DBeaver Ultimate Edtion 26.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具 One tool for all data sources 请访问原文链接&#xff1a;https://sysin.org/blog/dbeaver/ 查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 通用…...

一文讲透|9个降AIGC平台测评:本科生降AI率必备指南

在当今学术写作中&#xff0c;AI生成内容&#xff08;AIGC&#xff09;的广泛应用让越来越多的本科生面临“AI痕迹过重”和“查重率过高”的双重挑战。无论是课程论文、毕业设计还是科研项目&#xff0c;如何有效降低AI痕迹&#xff0c;同时保持文章逻辑清晰、语言自然&#xf…...