小程序图片预览优化:只加载当前和滑动到的图片
在做小程序开发时,我们经常会遇到这样的场景: 点击图片列表中的某一张,跳转到预览页面,通过 swiper 组件实现图片轮播预览。
一个常见的问题是: 如果一次性传了一整个图片数组,swiper-item 中每个 image 都会去加载网络资源,导致首屏渲染慢、流量浪费。
那么有没有办法优化,只加载当前需要看的那几张图片呢?
1.思路分析
1.入口页传递 ID 点击列表图片时传递当前图片的 _id 给预览页。预览页接收 ID 在 onLoad 中通过传入的 id 去缓存的 classList 找到对应图片的下标 index。
2.定义一个已读数组 用一个 ref([]) 存储需要加载的图片索引,比如 readImg。
3.初始化加载 首次进入时,把传入的图片下标 index 推入 readImg,这样只会加载这一张。
4.监听轮播变化 swiper 的 @change 事件触发时,获取新的下标 current,再推入 readImg。
条件渲染 在模板中,image 标签通过 v-if="readImg.includes(index)" 判断,只加载出现在数组里的图片。
这样就能保证:
👉 进入时只加载当前图片
👉 滑动到哪一张再加载哪一张
👉 大幅减少首屏加载压力
2.代码实现
<navigator :url="/pages/preview/preview?id=${item._id}" class="item" v-for="item in classList" :key="item._id">
<image :src="item.smallPicurl" mode="aspectFill"></image>
</navigator>
首先跳转的时候传递item.id用来筛选当前index。效果图
编辑
编辑
通过id去进行逻辑判断。
<template>
<view class="preview" v-if="currentInfo">
<swiper circular :current="currentIndex" @change="swiperChange" >
<swiper-item v-for="(item,index) in classList" :key="item._id">
<image v-if="readImg.includes(index)" @click="maskChange" :src="item.picUrl" mode="aspectFill"></image>
</swiper-item>
</swiper>
<view class="mask" v-if="maskState">
<view class="goBack" :style="{top:getStatusBarHeight()+'px'}" @click="goBack">
<uni-icons type="back" color="#fff" size="20"></uni-icons>
</view>
<view class="count">
{{currentIndex+1}} / {{classList.length}}
</view>
<view class="time">
<uni-dateformat :date="new Date()" format="hh:mm"></uni-dateformat>
</view>
<view class="date">
<!-- 一般用法 -->
<uni-dateformat :date="new Date()" format="MM月dd月"></uni-dateformat>
</view>
<view class="footer" v-if="currentInfo">
<view class="box" @click="clickInfo">
<uni-icons type="info" size="28"></uni-icons>
<view class="text">
信息
</view>
</view>
<view class="box" @click="clickScore">
<uni-icons type="star" size="28"></uni-icons>
<view class="text">
{{currentInfo.score}}分
</view>
</view>
<view class="box" @click="clickDownload">
<uni-icons type="download" size="28"></uni-icons>
<view class="text">
下载
</view>
</view>
</view>
</view>
<uni-popup ref="infoPopup" type="bottom">
<view class="infoPopup">
<view class="popHeader">
<view class="">
</view>
<view class="title">
壁纸信息
</view>
<view class="close" @click="clickInfoClose">
<uni-icons type="closeempty" size="18" color="#999"></uni-icons>
</view>
</view>
<scroll-view scroll-y>
<view class="content" v-if="currentInfo">
<view class="row">
<view class="label">
壁纸ID:
</view>
<text user-select class="value">{{currentInfo._id}}</text>
</view>
<view class="row">
<view class="label">
分类:
</view>
<text selectable class="value class">图片</text>
</view>
<view class="row">
<view class="label">
发布者:
</view>
<text selectable class="value">{{currentInfo.nickname}}</text>
</view>
<view class="row">
<view class="label">
评分:
</view>
<view class="value roteBox">
<uni-rate readonly :value="currentInfo.score" size="16" touchable />
<text class="score">{{currentInfo.score}}</text>
</view>
</view>
<view class="row">
<view class="label">
摘要:
</view>
<view class="value">
{{currentInfo.description}}
</view>
</view>
<view class="row">
<view class="label">
标签:
</view>
<view class="value tabs">
<view class="tab" v-for="item in currentInfo.tabs " :key="item">
{{item}}
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="scorePopup" :is-mask-click="false">
<view class="scorePopup">
<view class="popHeader">
<view class="">
</view>
<view class="title">
{{isScore?"评分过了":"评分壁纸"}}
</view>
<view class="close" @click="clickScoreClose">
<uni-icons type="closeempty" size="18" color="#999"></uni-icons>
</view>
</view>
<view class="content">
<uni-rate v-model="userScore" allowHalf :disabled="isScore" disabled-color="#FFCA3E" />
<text class="text">{{userScore}}分</text>
</view>
<view class="footer">
<button @click="submitScore" :disabled="!userScore || isScore" type="default" size="mini"
plain>确认评分</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app'
import {
getStatusBarHeight
} from '@/utils/system';
import {
apiGetSetupScore,
apiWriteDownload,
apiDetailWall
} from '@/api/apis.js'
import {onShareAppMessage,onShareTimeline} from '@dcloudio/uni-app'
const readImg = ref([])
const currentId = ref(null)
const classList = ref([])
const isScore = ref(false)
const storgClassList = uni.getStorageSync("storgClassList") || []
classList.value = storgClassList.map(item => {
return {
...item,
picUrl: item.smallPicurl.replace("_small.webp", ".jpg")
}
})
console.log(classList.value);
const maskState = ref(true)
const infoPopup = ref(null)
const scorePopup = ref(null)
const userScore = ref(0)
const currentIndex = ref(0)
const currentInfo = ref({})
//接收路径参数
onLoad(async(e) => {
console.log(e);
currentId.value = e.id
if(e.type==='share'){
let res =await apiDetailWall({
id:currentId.value
})
classList.value=res.data.map((item)=>{
return{
...item,
picUrl: item.smallPicurl.replace("_small.webp", ".jpg")
}
})
}
currentIndex.value = classList.value.findIndex(item => {
return item._id === currentId.value
})
currentInfo.value = classList.value[currentIndex.value]
readImg.value.push(currentIndex.value)
})
//滑动修改索引值
const swiperChange = (e) => {
currentIndex.value = e.detail.current
readImg.value.push(currentIndex.value)
currentInfo.value = classList.value[currentIndex.value]
}
//点击打开评分弹窗
const clickScore = () => {
if (currentInfo.value.userScore) {
isScore.value = true
userScore.value = currentInfo.value.userScore
}
scorePopup.value.open()
}
//关闭评分窗
const clickScoreClose = () => {
scorePopup.value.close()
userScore.value = 0
isScore.value = false
}
//点击关闭弹窗
const clickInfoClose = () => {
infoPopup.value.close()
}
//点击触发info弹窗
const clickInfo = () => {
infoPopup.value.open()
}
//遮罩层打开关闭控制
const maskChange = () => {
maskState.value = !maskState.value
}
//确认评分
const submitScore = async () => {
let {
classid,
_id: wallId
} = currentInfo.value
let res = await apiGetSetupScore({
classid,
wallId,
userScore: userScore.value
})
if (res.errCode === 0) {
uni.showToast({
title: "打分成功"
})
}
classList.value[currentIndex.value].userScore = userScore.value
uni.setStorageSync("storgClassList", classList.value)
scorePopup.value.close()
}
//返回上一页
const goBack = () => {
uni.navigateBack()
}
//点击下载
const clickDownload = async () => {
// #ifdef H5
uni.showModal({
content: "请长按保存壁纸",
showCancel: false
})
// #endif
// #ifndef H5
try {
uni.showLoading({
title: "下载中..",
mask: true
})
let {
classid,
_id: wallId
} = currentInfo.value
let res = await apiWriteDownload({
classid,
wallId
})
if (res.errCode !== 0) {
throw res
}
uni.getImageInfo({
src: currentInfo.value.picUrl,
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.path,
success: (res) => {
uni.showToast({
title: "保存成功,到相册查看",
icon: "none"
})
},
fail: (err) => {
if (err.errMsg === 'saveImageToPhotosAlbum:fail cancel') {
uni.showToast({
title: "保存失败,请重新下载",
icon: "none"
})
return
}
uni.showModal({
title: "授权提示",
content: "需要授权保存相册",
success: (res) => {
if (res.confirm) {
uni.openSetting({
success: (setting) => {
if (setting
.authSetting[
'scope.writePhotosAlbum'
]) {
uni.showToast({
title: "获取授权成功",
icon: 'none'
})
} else {
uni.showToast({
title: "获取授权失败",
icon: 'none'
})
}
}
})
}
}
})
},
complete: () => {
uni.hideLoading()
}
})
}
})
} catch (err) {
uni.hideLoading()
}
// #endif
}
//分享给好友
onShareAppMessage((e)=>{
return {
title:"壁纸wallpaper",
path:"/pages/preview/preview?id="+currentId.value+"&type=share"
}
})
//分享给朋友圈
onShareTimeline(()=>{
return {
title:"壁纸wallpaper",
query:"id="+currentId.value
}
})
</script>
首先通过id去筛选当前index,那么我们就可以通过下标获取到缓存中的图片列表中的我们当前预览的图片,轮播图动态改变下标也会改变currentIndex,这样就可以实现我们的预览图片效果,懒加载我们只希望加载预览过的图片,那么我们可以给image设置渲染条件 index===currintIndex但是这样我们滑动会有空白,而且浏览过的还是空白,我们用一个数组不管是滑动还是第一次点击预览都会把indexpush进去,那么在展示渲染的时候v-if只需要数组includes包含当前index就可以了。