123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- <template>
- <view class="zs-lazy-image" :style="[imgViewStyle]"
- :class="['zs-lazy-item-' + elIndex,loadStatus=='loaded'?'':'will-change']">
- <view :class="'zs-lazy-item-' + elIndex" class="zs-lazy-item-goods" style="font-size: 0;">
-
- <image :style="[imgStyle]" v-if="!isError" class="zs-lazy-item" :src="isShow ? image : loadingImg"
- :mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg">
- </image>
- <image :style="[imgStyle]" class="zs-lazy-item error" v-else :src="loadingImg" :mode="imgMode"
- @load="errorImgLoaded" @tap="clickImg"></image>
- </view>
- </view>
-
- </template>
- <script>
- import guid from '@/utils/guid.js'
- export default {
- name: 'ZsLazyLoad',
- props: {
- // 当前item的索引
- index: {
- type: [Number, String]
- },
- // 要显示的图片
- image: {
- type: String,
- default: ''
- },
- // 图片裁剪模式
- imgMode: {
- type: String,
- default: 'aspectFill'
- },
- // 占位图片路径
- loadingImg: {
- type: String,
- default: '/static/logo.png'
- },
- // 加载失败的错误占位图
- errorImg: {
- type: String,
- default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162t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'
- },
- // 图片进入可见区域前多少像素时,单位rpx,开始加载图片
- // 负数为图片超出屏幕底部多少距离后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上)
- threshold: {
- type: [Number, String],
- default: 30
- },
- // 淡入淡出动画的过渡时间
- duration: {
- type: [Number, String],
- default: 200
- },
- // 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显
- // linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
- effect: {
- type: String,
- default: 'ease-in-out'
- },
- // 是否使用过渡效果
- isEffect: {
- type: Boolean,
- default: false
- },
- // 圆角值
- borderRadius: {
- type: [Number, String],
- default: 20
- },
- // 图片高度,单位rpx
- height: {
- type: [Number, String],
- default: '450'
- },
- width:{
- type:[Number,String],
- default:'auto'
- },
- // full 全部圆角 default 上右圆角
- type: {
- type: String,
- default: 'full'
- },
- },
- data() {
- return {
- isShow: false,
- opacity: 1,
- time: this.duration,
- isError: false, // 图片加载失败
- elIndex: guid()
- }
- },
- computed: {
- imageCopy() {
- let _img = this.image.replace('_640', '_320')
- return _img
- },
- // 将threshold从rpx转为px
- getThreshold() {
- // 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原
- let thresholdPx = uni.upx2px(Math.abs(this.threshold));
- return this.threshold < 0 ? -thresholdPx : thresholdPx;
- },
- imgStyle() {
- return {
- borderRadius: this.type == 'full' ? this.borderRadius + 'rpx' :
- `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`,
- height: this.height == 'auto' ? 'auto' : String(this.height).indexOf('%') != -1 ? this.height : this
- .height + 'rpx',
- width:this.width == 'auto' ? 'auto' : String(this.width).indexOf('%') != -1 ? this.width : this
- .width + 'rpx',
- }
- },
- imgViewStyle() {
- return {
- opacity: Number(this.opacity),
- borderRadius: this.type == 'full' ? this.borderRadius + 'rpx' :
- `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`,
- transition: `opacity ${this.time / 1000}s ease-in-out`
- }
- }
- },
- watch: {
- isShow(nVal) {
- // 如果是不开启过渡效果,直接返回
- if (!this.isEffect) return;
- this.time = 0;
- // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果
- this.opacity = 0.7;
- // 延时30ms,否则在浏览器H5,过渡效果无效
- setTimeout(() => {
- this.time = this.duration;
- this.opacity = 1;
- }, 30)
- }
- },
- mounted() {
- // 此hdOnReachBottom事件由mixin.js发出,目的是让页面到底时,保证所有图片都进行加载,做到绝对稳定且可靠
- this.$nextTick(() => {
- uni.$once('hdOnReachBottom', () => {
- if (!this.isShow) this.isShow = true;
- });
- })
- // mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果
- setTimeout(() => {
- // 这里是组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver
- this.disconnectObserver('contentObserver');
- const contentObserver = uni.createIntersectionObserver(this);
- // 要理解这里怎么计算的,请看这个:
- // https://blog.csdn.net/qq_25324335/article/details/83687695
- contentObserver.relativeToViewport({
- bottom: this.getThreshold,
- }).observe('.zs-lazy-item-' + this.elIndex, (res) => {
- if (res.intersectionRatio > 0) {
- // 懒加载状态改变
- this.isShow = true;
- // 如果图片已经加载,去掉监听,减少性能的消耗
- this.disconnectObserver('contentObserver');
- }
- })
- this.contentObserver = contentObserver;
- }, 30)
- },
- methods: {
- // 点击图片触发的事件,loadlazy-还是懒加载中状态,loading-图片正在加载,loaded-图片加加载完成
- clickImg() {
- let whichImg = '';
- // 如果isShow为false,意味着图片还没开始加载,点击的只能是最开始的占位图
- if (this.isShow == false) whichImg = 'lazyImg';
- // 如果isError为true,意味着图片加载失败,这是只剩下错误的占位图,所以点击的只能是错误占位图
- // 当然,也可以给错误的占位图元素绑定点击事件,看你喜欢~
- else if (this.isError == true) whichImg = 'errorImg';
- // 总共三张图片,除了两个占位图,剩下的只能是正常的那张图片了
- else whichImg = 'realImg';
- // 只通知当前图片的index
- this.$emit('click', this.index);
- },
- // 图片加载完成事件,可能是加载占位图时触发,也可能是加载真正的图片完成时触发,通过isShow区分
- imgLoaded() {
- // 占位图加载完成
- if (!this.loadStatus) {
- this.loadStatus = 'lazyed';
- }
- // 真正的图片加载完成
- else if (this.loadStatus == 'lazyed') {
- this.loadStatus = 'loaded';
- this.$emit('load', this.index);
- }
- },
- // 错误的图片加载完成
- errorImgLoaded() {
- this.$emit('error', this.index);
- },
- // 图片加载失败
- loadError() {
- this.isError = true;
- },
- disconnectObserver(observerName) {
- const observer = this[observerName];
- observer && observer.disconnect();
- },
- },
- beforeDestroy() {
- this.disconnectObserver('contentObserver');
- }
- }
- </script>
- <style lang="scss" scoped>
- .zs-lazy-image {
- background-color: #f5f5f6;
- overflow: hidden;
- -webkit-transform: rotate(0deg);
- }
- .zs-lazy-item-goods{
- position: relative;
- }
- .tui_lable{
- width: 120rpx;
- height: 32rpx;
- padding-right: 6rpx;
- box-sizing: border-box;
- display: flex;
- align-items: center;
- justify-content: center;
- position: absolute;
- left: -16px;
- top: 5px;
- transform: rotate(-45deg);
- font-size: 24rpx;
- color: #fff;
- }
- // 通过class控制,image loaded后移除,释放内存,
- // 避免大量will-change挂载
- .will-change {
- transform: transition3d(0, 0, 0);
- will-change: transform, opacity;
- }
- .zs-lazy-item {
- width: 100%;
- // 骗系统开启硬件加速, hack写法,交由will-change处理
- // transform: transition3d(0, 0, 0);
- // 防止图片加载“闪一下”
- // will-change: transform;
- display: block;
- overflow: hidden;
- }
- </style>
|