u-swiper.vue 9.72 KB
Newer Older
baihong committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
<template>
	<view class="u-swiper-wrap" :style="{
		borderRadius: `${borderRadius}rpx`
	}">
		<swiper :current="elCurrent" @change="change" @animationfinish="animationfinish" :interval="interval" :circular="circular" :duration="duration" :autoplay="autoplay"
		 :previous-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'" :next-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'"
		 :style="{
				height: height + 'rpx',
				backgroundColor: bgColor
			}">
			<swiper-item class="u-swiper-item" v-for="(item, index) in list" :key="index">
				<view class="u-list-image-wrap" @tap.stop.prevent="listClick(index)" :class="[uCurrent != index ? 'u-list-scale' : '']" :style="{
						borderRadius: `${borderRadius}rpx`,
						transform: effect3d && uCurrent != index ? 'scaleY(0.9)' : 'scaleY(1)',
						margin: effect3d && uCurrent != index ? '0 20rpx' : 0,
					}">
					<image class="u-swiper-image" :src="item[name] || item" :mode="imgMode"></image>
					<view v-if="title && item.title" class="u-swiper-title u-line-1" :style="[{
							'padding-bottom': titlePaddingBottom
						}, titleStyle]">
						{{ item.title }}
					</view>
				</view>
			</swiper-item>
		</swiper>
		<view class="u-swiper-indicator" :style="{
				top: indicatorPos == 'topLeft' || indicatorPos == 'topCenter' || indicatorPos == 'topRight' ? '12rpx' : 'auto',
				bottom: indicatorPos == 'bottomLeft' || indicatorPos == 'bottomCenter' || indicatorPos == 'bottomRight' ? '12rpx' : 'auto',
				justifyContent: justifyContent,
				padding: `0 ${effect3d ? '74rpx' : '24rpx'}`
			}">
			<block v-if="mode == 'rect'">
				<view class="u-indicator-item-rect" :class="{ 'u-indicator-item-rect-active': index == uCurrent }" v-for="(item, index) in list"
				 :key="index"></view>
			</block>
			<block v-if="mode == 'dot'">
				<view class="u-indicator-item-dot" :class="{ 'u-indicator-item-dot-active': index == uCurrent }" v-for="(item, index) in list"
				 :key="index"></view>
			</block>
			<block v-if="mode == 'round'">
				<view class="u-indicator-item-round" :class="{ 'u-indicator-item-round-active': index == uCurrent }" v-for="(item, index) in list"
				 :key="index"></view>
			</block>
			<block v-if="mode == 'number'">
				<view class="u-indicator-item-number">{{ uCurrent + 1 }}/{{ list.length }}</view>
			</block>
		</view>
	</view>
</template>

<script>
	/**
	 * swiper 轮播图
	 * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用
	 * @tutorial https://www.uviewui.com/components/swiper.html
	 * @property {Array} list 轮播图数据,见官网"基本使用"说明
	 * @property {Boolean} title 是否显示标题文字,需要配合list参数,见官网说明(默认false)
	 * @property {String} mode 指示器模式,见官网说明(默认round)
	 * @property {String Number} height 轮播图组件高度,单位rpx(默认250)
	 * @property {String} indicator-pos 指示器的位置(默认bottomCenter)
	 * @property {Boolean} effect3d 是否开启3D效果(默认false)
	 * @property {Boolean} autoplay 是否自动播放(默认true)
	 * @property {String Number} interval 自动轮播时间间隔,单位ms(默认2500)
	 * @property {Boolean} circular 是否衔接播放,见官网说明(默认true)
	 * @property {String} bg-color 背景颜色(默认#f3f4f6)
	 * @property {String Number} border-radius 轮播图圆角值,单位rpx(默认8)
	 * @property {Object} title-style 自定义标题样式
	 * @property {String Number} effect3d-previous-margin mode = true模式的情况下,激活项与前后项之间的距离,单位rpx(默认50)
	 * @property {String} img-mode 图片的裁剪模式,详见image组件裁剪模式(默认aspectFill)
	 * @event {Function} click 点击轮播图时触发
	 * @example <u-swiper :list="list" mode="dot" indicator-pos="bottomRight"></u-swiper>
	 */
	export default {
		name: "u-swiper",
		props: {
			// 轮播图的数据,格式如:[{image: 'xxxx', title: 'xxxx'},{image: 'yyyy', title: 'yyyy'}],其中title字段可选
			list: {
				type: Array,
				default () {
					return [];
				}
			},
			// 是否显示title标题
			title: {
				type: Boolean,
				default: false
			},
			// 用户自定义的指示器的样式
			indicator: {
				type: Object,
				default () {
					return {};
				}
			},
			// 圆角值
			borderRadius: {
				type: [Number, String],
				default: 8
			},
			// 隔多久自动切换
			interval: {
				type: [String, Number],
				default: 3000
			},
			// 指示器的模式,rect|dot|number|round
			mode: {
				type: String,
				default: 'round'
			},
			// list的高度,单位rpx
			height: {
				type: [Number, String],
				default: 250
			},
			// 指示器的位置,topLeft|topCenter|topRight|bottomLeft|bottomCenter|bottomRight
			indicatorPos: {
				type: String,
				default: 'bottomCenter'
			},
			// 是否开启缩放效果
			effect3d: {
				type: Boolean,
				default: false
			},
			// 3D模式的情况下,激活item与前后item之间的距离,单位rpx
			effect3dPreviousMargin: {
				type: [Number, String],
				default: 50
			},
			// 是否自动播放
			autoplay: {
				type: Boolean,
				default: true
			},
			// 自动轮播时间间隔,单位ms
			duration: {
				type: [Number, String],
				default: 500
			},
			// 是否衔接滑动,即到最后一张时接着滑动,是否自动切换到第一张
			circular: {
				type: Boolean,
				default: true
			},
			// 图片的裁剪模式 
			imgMode: {
				type: String,
				default: 'aspectFill'
			},
			// 从list数组中读取的图片的属性名
			name: {
				type: String,
				default: 'image'
			},
			// 背景颜色
			bgColor: {
				type: String,
				default: '#f3f4f6'
			},
			// 初始化时,默认显示第几项
			current: {
				type: [Number, String],
				default: 0
			},
			// 标题的样式,对象形式
			titleStyle: {
				type: Object,
				default() {
					return {}
				}
			}
		},
		watch: {
			// 如果外部的list发生变化,判断长度是否被修改,如果前后长度不一致,重置uCurrent值,避免溢出
			list(nVal, oVal) {
				if(nVal.length !== oVal.length) this.uCurrent = 0;
			},
			// 监听外部current的变化,实时修改内部依赖于此测uCurrent值,如果更新了current,而不是更新uCurrent,
			// 就会错乱,因为指示器是依赖于uCurrent的
			current(n) {
				this.uCurrent = n;
			}
		},
		data() {
			return {
				uCurrent: this.current // 当前活跃的swiper-item的index
			};
		},
		computed: {
			justifyContent() {
				if (this.indicatorPos == 'topLeft' || this.indicatorPos == 'bottomLeft') return 'flex-start';
				if (this.indicatorPos == 'topCenter' || this.indicatorPos == 'bottomCenter') return 'center';
				if (this.indicatorPos == 'topRight' || this.indicatorPos == 'bottomRight') return 'flex-end';
			},
			titlePaddingBottom() {
				let tmp = 0;
				if (this.mode == 'none') return '12rpx';
				if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode == 'number') {
					tmp = '60rpx';
				} else if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode != 'number') {
					tmp = '40rpx';
				} else {
					tmp = '12rpx';
				}
				return tmp;
			},
			// 因为uni的swiper组件的current参数只接受Number类型,这里做一个转换
			elCurrent() {
				return Number(this.current);
			}
		},
		methods: {
			listClick(index) {
				this.$emit('click', index);
			},
			change(e) {
				let current = e.detail.current;
				this.uCurrent = current;
				// 发出change事件,表示当前自动切换的index,从0开始
				this.$emit('change', current);
			},
			// 头条小程序不支持animationfinish事件,改由change事件
			// 暂不监听此事件,因为不再给swiper绑定uCurrent属性
			animationfinish(e) {
				// #ifndef MP-TOUTIAO
				// this.uCurrent = e.detail.current;
				// #endif
			}
		}
	};
</script>

<style lang="scss" scoped>
	@import "../../libs/css/style.components.scss";
	
	.u-swiper-wrap {
		position: relative;
		overflow: hidden;
		transform: translateY(0);
	}

	.u-swiper-image {
		width: 100%;
		will-change: transform;
		height: 100%;
		/* #ifndef APP-NVUE */
		display: block;
		/* #endif */
		/* #ifdef H5 */
		pointer-events: none;
		/* #endif */
	}

	.u-swiper-indicator {
		padding: 0 24rpx;
		position: absolute;
		@include vue-flex;
		width: 100%;
		z-index: 1;
	}

	.u-indicator-item-rect {
		width: 26rpx;
		height: 8rpx;
		margin: 0 6rpx;
		transition: all 0.5s;
		background-color: rgba(0, 0, 0, 0.3);
	}

	.u-indicator-item-rect-active {
		background-color: rgba(255, 255, 255, 0.8);
	}

	.u-indicator-item-dot {
		width: 14rpx;
		height: 14rpx;
		margin: 0 6rpx;
		border-radius: 20rpx;
		transition: all 0.5s;
		background-color: rgba(0, 0, 0, 0.3);
	}

	.u-indicator-item-dot-active {
		background-color: rgba(255, 255, 255, 0.8);
	}

	.u-indicator-item-round {
		width: 14rpx;
		height: 14rpx;
		margin: 0 6rpx;
		border-radius: 20rpx;
		transition: all 0.5s;
		background-color: rgba(0, 0, 0, 0.3);
	}

	.u-indicator-item-round-active {
		width: 34rpx;
		background-color: rgba(255, 255, 255, 0.8);
	}

	.u-indicator-item-number {
		padding: 6rpx 16rpx;
		line-height: 1;
		background-color: rgba(0, 0, 0, 0.3);
		border-radius: 100rpx;
		font-size: 26rpx;
		color: rgba(255, 255, 255, 0.8);
	}

	.u-list-scale {
		transform-origin: center center;
	}

	.u-list-image-wrap {
		width: 100%;
		height: 100%;
		flex: 1;
		transition: all 0.5s;
		overflow: hidden;
		box-sizing: content-box;
		position: relative;
	}

	.u-swiper-title {
		position: absolute;
		background-color: rgba(0, 0, 0, 0.3);
		bottom: 0;
		left: 0;
		width: 100%;
		font-size: 28rpx;
		padding: 12rpx 24rpx;
		color: rgba(255, 255, 255, 0.9);
	}

	.u-swiper-item {
		@include vue-flex;
		overflow: hidden;
		align-items: center;
	}
</style>