2026-03-23 14:57:47 +08:00

300 lines
5.7 KiB
Vue

<template>
<view class="easy-loadimage" :style="[boxStyle]" :id="uid">
<image
class="origin-img"
:src="imageSrc"
mode="aspectFill"
v-if="loadImg && !isLoadError"
v-show="showImg"
:style="[imgStyle]"
:class="{
'no-transition': !openTransition,
'show-transition': showTransition && openTransition,
}"
@load="handleImgLoad"
@error="handleImgError"
>
</image>
<image
class="border-img"
:src="borderSrc"
mode="aspectFill"
v-if="loadImg && !isLoadError && borderSrc"
v-show="showImg"
:style="[imgStyle]"
:class="{
'no-transition': !openTransition,
'show-transition': showTransition && openTransition,
}"
>
</image>
<view class="loadfail-img" v-else-if="isLoadError"></view>
<view
:class="['loading-img', 'spin-circle', loadingMode]"
v-show="!showImg && !isLoadError"
></view>
</view>
</template>
<script>
import { Throttle } from "@/utils/validate.js";
// 生成全局唯一id
function generateUUID() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
let r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default {
props: {
imageSrc: {
type: String,
default: "",
},
borderSrc: {
type: String,
default: "",
},
mode: {
type: String,
default: "",
},
loadingMode: {
type: String,
default: "looming-gray",
},
openTransition: {
type: Boolean,
default: true,
},
viewHeight: {
type: Number,
default() {
return uni.getWindowInfo().windowHeight;
},
},
width: {
type: String,
default: "",
},
height: {
type: String,
default: "",
},
borderRadius: {
type: String,
default: "",
},
},
data() {
const that = this;
return {
// uid:'',
uid: "uid-" + generateUUID(),
loadImg: false,
showImg: false,
isLoadError: false,
borderLoaded: 0,
showTransition: false,
scrollFn: Throttle(function () {
// 加载img时才执行滚动监听判断是否可加载
if (that.loadImg || that.isLoadError) return;
const id = that.uid;
const query = uni.createSelectorQuery().in(that);
query
.select("#" + id)
.boundingClientRect((data) => {
if (!data) return;
if (data.top - that.viewHeight < 0) {
that.loadImg = !!that.imageSrc;
that.isLoadError = !that.loadImg;
}
})
.exec();
}, 200),
};
},
computed: {
boxStyle() {
return {
width: this.width,
height: this.height,
borderRadius: this.borderRadius,
};
},
imgStyle() {
return {
borderRadius: this.borderRadius,
};
},
},
methods: {
init() {
this.$nextTick(this.onScroll);
},
handleBorderLoad() {
this.borderLoaded = 1;
},
handleBorderError() {
this.borderLoaded = 2;
},
handleImgLoad(e) {
this.showImg = true;
// this.$nextTick(function(){
// this.showTransition = true
// })
setTimeout(() => {
this.showTransition = true;
}, 50);
},
handleImgError(e) {
this.isLoadError = true;
},
onScroll() {
this.scrollFn();
},
},
mounted() {
this.init();
uni.$on("scroll", this.scrollFn);
this.onScroll();
},
beforeDestroy() {
uni.$off("scroll", this.scrollFn);
},
};
</script>
<style scoped>
.easy-loadimage {
position: relative;
}
.border-img {
position: absolute;
width: 100%;
height: 100%;
/* max-height: 360rpx; */
top: 0;
left: 0;
}
/* 官方优化图片tips */
image {
will-change: transform;
}
/* 渐变过渡效果处理 */
image.origin-img {
width: 100%;
height: 100%;
opacity: 0.3;
/* max-height: 360rpx; */
}
image.origin-img.show-transition {
transition: opacity 0.5s;
opacity: 1;
}
image.origin-img.no-transition {
opacity: 1;
}
/* 渐变过渡效果处理 */
image.border-img {
width: 100%;
height: 100%;
opacity: 0.3;
/* max-height: 360rpx; */
}
image.border-img.show-transition {
transition: opacity 0.5s;
opacity: 1;
}
image.border-img.no-transition {
opacity: 1;
}
/* 加载失败、加载中的占位图样式控制 */
.loadfail-img {
height: 100%;
background: url("~@/static/easy-loadimage/loadfail.png") no-repeat center;
background-size: 50%;
}
.loading-img {
height: 100%;
}
/* 转圈 */
.spin-circle {
background: url("~@/static/easy-loadimage/loading.png") no-repeat center;
background-size: 60%;
}
/* 动态灰色若隐若现 */
.looming-gray {
animation: looming-gray 1s infinite linear;
background-color: #e3e3e3;
border-radius: 12rpx;
}
@keyframes looming-gray {
0% {
background-color: #e3e3e3aa;
}
50% {
background-color: #e3e3e3;
}
100% {
background-color: #e3e3e3aa;
}
}
/* 骨架屏1 */
.skeleton-1 {
background-color: #e3e3e3;
background-image: linear-gradient(
100deg,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0) 80%
);
background-size: 100rpx 100%;
background-repeat: repeat-y;
background-position: 0 0;
animation: skeleton-1 0.6s infinite;
}
@keyframes skeleton-1 {
to {
background-position: 200% 0;
}
}
/* 骨架屏2 */
.skeleton-2 {
background-image: linear-gradient(
-90deg,
#fefefe 0%,
#e6e6e6 50%,
#fefefe 100%
);
background-size: 400% 400%;
background-position: 0 0;
animation: skeleton-2 1.2s ease-in-out infinite;
}
@keyframes skeleton-2 {
to {
background-position: -135% 0;
}
}
</style>