guangchao.xu | 070005a | 2020-12-07 09:56:40 +0800 | [diff] [blame^] | 1 | <template> |
| 2 | <view class="u-swiper-wrap" :style="{ |
| 3 | borderRadius: `${borderRadius}rpx` |
| 4 | }"> |
| 5 | <swiper :current="elCurrent" @change="change" @animationfinish="animationfinish" :interval="interval" :circular="circular" :duration="duration" :autoplay="autoplay" |
| 6 | :previous-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'" :next-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'" |
| 7 | :style="{ |
| 8 | height: height + 'rpx', |
| 9 | backgroundColor: bgColor |
| 10 | }"> |
| 11 | <swiper-item class="u-swiper-item" v-for="(item, index) in list" :key="index"> |
| 12 | <view class="u-list-image-wrap" @tap.stop.prevent="listClick(index)" :class="[uCurrent != index ? 'u-list-scale' : '']" :style="{ |
| 13 | borderRadius: `${borderRadius}rpx`, |
| 14 | transform: effect3d && uCurrent != index ? 'scaleY(0.9)' : 'scaleY(1)', |
| 15 | margin: effect3d && uCurrent != index ? '0 20rpx' : 0, |
| 16 | }"> |
| 17 | <image class="u-swiper-image" :src="item[name] || item" :mode="imgMode"></image> |
| 18 | <view v-if="title && item.title" class="u-swiper-title u-line-1" :style="[{ |
| 19 | 'padding-bottom': titlePaddingBottom |
| 20 | }, titleStyle]"> |
| 21 | {{ item.title }} |
| 22 | </view> |
| 23 | </view> |
| 24 | </swiper-item> |
| 25 | </swiper> |
| 26 | <view class="u-swiper-indicator" :style="{ |
| 27 | top: indicatorPos == 'topLeft' || indicatorPos == 'topCenter' || indicatorPos == 'topRight' ? '12rpx' : 'auto', |
| 28 | bottom: indicatorPos == 'bottomLeft' || indicatorPos == 'bottomCenter' || indicatorPos == 'bottomRight' ? '12rpx' : 'auto', |
| 29 | justifyContent: justifyContent, |
| 30 | padding: `0 ${effect3d ? '74rpx' : '24rpx'}` |
| 31 | }"> |
| 32 | <block v-if="mode == 'rect'"> |
| 33 | <view class="u-indicator-item-rect" :class="{ 'u-indicator-item-rect-active': index == uCurrent }" v-for="(item, index) in list" |
| 34 | :key="index"></view> |
| 35 | </block> |
| 36 | <block v-if="mode == 'dot'"> |
| 37 | <view class="u-indicator-item-dot" :class="{ 'u-indicator-item-dot-active': index == uCurrent }" v-for="(item, index) in list" |
| 38 | :key="index"></view> |
| 39 | </block> |
| 40 | <block v-if="mode == 'round'"> |
| 41 | <view class="u-indicator-item-round" :class="{ 'u-indicator-item-round-active': index == uCurrent }" v-for="(item, index) in list" |
| 42 | :key="index"></view> |
| 43 | </block> |
| 44 | <block v-if="mode == 'number'"> |
| 45 | <view class="u-indicator-item-number">{{ uCurrent + 1 }}/{{ list.length }}</view> |
| 46 | </block> |
| 47 | </view> |
| 48 | </view> |
| 49 | </template> |
| 50 | |
| 51 | <script> |
| 52 | /** |
| 53 | * swiper 轮播图 |
| 54 | * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用 |
| 55 | * @tutorial https://www.uviewui.com/components/swiper.html |
| 56 | * @property {Array} list 轮播图数据,见官网"基本使用"说明 |
| 57 | * @property {Boolean} title 是否显示标题文字,需要配合list参数,见官网说明(默认false) |
| 58 | * @property {String} mode 指示器模式,见官网说明(默认round) |
| 59 | * @property {String Number} height 轮播图组件高度,单位rpx(默认250) |
| 60 | * @property {String} indicator-pos 指示器的位置(默认bottomCenter) |
| 61 | * @property {Boolean} effect3d 是否开启3D效果(默认false) |
| 62 | * @property {Boolean} autoplay 是否自动播放(默认true) |
| 63 | * @property {String Number} interval 自动轮播时间间隔,单位ms(默认2500) |
| 64 | * @property {Boolean} circular 是否衔接播放,见官网说明(默认true) |
| 65 | * @property {String} bg-color 背景颜色(默认#f3f4f6) |
| 66 | * @property {String Number} border-radius 轮播图圆角值,单位rpx(默认8) |
| 67 | * @property {Object} title-style 自定义标题样式 |
| 68 | * @property {String Number} effect3d-previous-margin mode = true模式的情况下,激活项与前后项之间的距离,单位rpx(默认50) |
| 69 | * @property {String} img-mode 图片的裁剪模式,详见image组件裁剪模式(默认aspectFill) |
| 70 | * @event {Function} click 点击轮播图时触发 |
| 71 | * @example <u-swiper :list="list" mode="dot" indicator-pos="bottomRight"></u-swiper> |
| 72 | */ |
| 73 | export default { |
| 74 | name: "u-swiper", |
| 75 | props: { |
| 76 | // 轮播图的数据,格式如:[{image: 'xxxx', title: 'xxxx'},{image: 'yyyy', title: 'yyyy'}],其中title字段可选 |
| 77 | list: { |
| 78 | type: Array, |
| 79 | default () { |
| 80 | return []; |
| 81 | } |
| 82 | }, |
| 83 | // 是否显示title标题 |
| 84 | title: { |
| 85 | type: Boolean, |
| 86 | default: false |
| 87 | }, |
| 88 | // 用户自定义的指示器的样式 |
| 89 | indicator: { |
| 90 | type: Object, |
| 91 | default () { |
| 92 | return {}; |
| 93 | } |
| 94 | }, |
| 95 | // 圆角值 |
| 96 | borderRadius: { |
| 97 | type: [Number, String], |
| 98 | default: 8 |
| 99 | }, |
| 100 | // 隔多久自动切换 |
| 101 | interval: { |
| 102 | type: [String, Number], |
| 103 | default: 3000 |
| 104 | }, |
| 105 | // 指示器的模式,rect|dot|number|round |
| 106 | mode: { |
| 107 | type: String, |
| 108 | default: 'round' |
| 109 | }, |
| 110 | // list的高度,单位rpx |
| 111 | height: { |
| 112 | type: [Number, String], |
| 113 | default: 250 |
| 114 | }, |
| 115 | // 指示器的位置,topLeft|topCenter|topRight|bottomLeft|bottomCenter|bottomRight |
| 116 | indicatorPos: { |
| 117 | type: String, |
| 118 | default: 'bottomCenter' |
| 119 | }, |
| 120 | // 是否开启缩放效果 |
| 121 | effect3d: { |
| 122 | type: Boolean, |
| 123 | default: false |
| 124 | }, |
| 125 | // 3D模式的情况下,激活item与前后item之间的距离,单位rpx |
| 126 | effect3dPreviousMargin: { |
| 127 | type: [Number, String], |
| 128 | default: 50 |
| 129 | }, |
| 130 | // 是否自动播放 |
| 131 | autoplay: { |
| 132 | type: Boolean, |
| 133 | default: true |
| 134 | }, |
| 135 | // 自动轮播时间间隔,单位ms |
| 136 | duration: { |
| 137 | type: [Number, String], |
| 138 | default: 500 |
| 139 | }, |
| 140 | // 是否衔接滑动,即到最后一张时接着滑动,是否自动切换到第一张 |
| 141 | circular: { |
| 142 | type: Boolean, |
| 143 | default: true |
| 144 | }, |
| 145 | // 图片的裁剪模式 |
| 146 | imgMode: { |
| 147 | type: String, |
| 148 | default: 'aspectFill' |
| 149 | }, |
| 150 | // 从list数组中读取的图片的属性名 |
| 151 | name: { |
| 152 | type: String, |
| 153 | default: 'image' |
| 154 | }, |
| 155 | // 背景颜色 |
| 156 | bgColor: { |
| 157 | type: String, |
| 158 | default: '#f3f4f6' |
| 159 | }, |
| 160 | // 初始化时,默认显示第几项 |
| 161 | current: { |
| 162 | type: [Number, String], |
| 163 | default: 0 |
| 164 | }, |
| 165 | // 标题的样式,对象形式 |
| 166 | titleStyle: { |
| 167 | type: Object, |
| 168 | default() { |
| 169 | return {} |
| 170 | } |
| 171 | } |
| 172 | }, |
| 173 | watch: { |
| 174 | // 如果外部的list发生变化,判断长度是否被修改,如果前后长度不一致,重置uCurrent值,避免溢出 |
| 175 | list(nVal, oVal) { |
| 176 | if(nVal.length !== oVal.length) this.uCurrent = 0; |
| 177 | }, |
| 178 | // 监听外部current的变化,实时修改内部依赖于此测uCurrent值,如果更新了current,而不是更新uCurrent, |
| 179 | // 就会错乱,因为指示器是依赖于uCurrent的 |
| 180 | current(n) { |
| 181 | this.uCurrent = n; |
| 182 | } |
| 183 | }, |
| 184 | data() { |
| 185 | return { |
| 186 | uCurrent: this.current // 当前活跃的swiper-item的index |
| 187 | }; |
| 188 | }, |
| 189 | computed: { |
| 190 | justifyContent() { |
| 191 | if (this.indicatorPos == 'topLeft' || this.indicatorPos == 'bottomLeft') return 'flex-start'; |
| 192 | if (this.indicatorPos == 'topCenter' || this.indicatorPos == 'bottomCenter') return 'center'; |
| 193 | if (this.indicatorPos == 'topRight' || this.indicatorPos == 'bottomRight') return 'flex-end'; |
| 194 | }, |
| 195 | titlePaddingBottom() { |
| 196 | let tmp = 0; |
| 197 | if (this.mode == 'none') return '12rpx'; |
| 198 | if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode == 'number') { |
| 199 | tmp = '60rpx'; |
| 200 | } else if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode != 'number') { |
| 201 | tmp = '40rpx'; |
| 202 | } else { |
| 203 | tmp = '12rpx'; |
| 204 | } |
| 205 | return tmp; |
| 206 | }, |
| 207 | // 因为uni的swiper组件的current参数只接受Number类型,这里做一个转换 |
| 208 | elCurrent() { |
| 209 | return Number(this.current); |
| 210 | } |
| 211 | }, |
| 212 | methods: { |
| 213 | listClick(index) { |
| 214 | this.$emit('click', index); |
| 215 | }, |
| 216 | change(e) { |
| 217 | let current = e.detail.current; |
| 218 | this.uCurrent = current; |
| 219 | // 发出change事件,表示当前自动切换的index,从0开始 |
| 220 | this.$emit('change', current); |
| 221 | }, |
| 222 | // 头条小程序不支持animationfinish事件,改由change事件 |
| 223 | // 暂不监听此事件,因为不再给swiper绑定uCurrent属性 |
| 224 | animationfinish(e) { |
| 225 | // #ifndef MP-TOUTIAO |
| 226 | // this.uCurrent = e.detail.current; |
| 227 | // #endif |
| 228 | } |
| 229 | } |
| 230 | }; |
| 231 | </script> |
| 232 | |
| 233 | <style lang="scss" scoped> |
| 234 | @import "../../libs/css/style.components.scss"; |
| 235 | |
| 236 | .u-swiper-wrap { |
| 237 | position: relative; |
| 238 | overflow: hidden; |
| 239 | transform: translateY(0); |
| 240 | } |
| 241 | |
| 242 | .u-swiper-image { |
| 243 | width: 100%; |
| 244 | will-change: transform; |
| 245 | height: 100%; |
| 246 | /* #ifndef APP-NVUE */ |
| 247 | display: block; |
| 248 | /* #endif */ |
| 249 | /* #ifdef H5 */ |
| 250 | pointer-events: none; |
| 251 | /* #endif */ |
| 252 | } |
| 253 | |
| 254 | .u-swiper-indicator { |
| 255 | padding: 0 24rpx; |
| 256 | position: absolute; |
| 257 | @include vue-flex; |
| 258 | width: 100%; |
| 259 | z-index: 1; |
| 260 | } |
| 261 | |
| 262 | .u-indicator-item-rect { |
| 263 | width: 26rpx; |
| 264 | height: 8rpx; |
| 265 | margin: 0 6rpx; |
| 266 | transition: all 0.5s; |
| 267 | background-color: rgba(0, 0, 0, 0.3); |
| 268 | } |
| 269 | |
| 270 | .u-indicator-item-rect-active { |
| 271 | background-color: rgba(255, 255, 255, 0.8); |
| 272 | } |
| 273 | |
| 274 | .u-indicator-item-dot { |
| 275 | width: 14rpx; |
| 276 | height: 14rpx; |
| 277 | margin: 0 6rpx; |
| 278 | border-radius: 20rpx; |
| 279 | transition: all 0.5s; |
| 280 | background-color: rgba(0, 0, 0, 0.3); |
| 281 | } |
| 282 | |
| 283 | .u-indicator-item-dot-active { |
| 284 | background-color: rgba(255, 255, 255, 0.8); |
| 285 | } |
| 286 | |
| 287 | .u-indicator-item-round { |
| 288 | width: 14rpx; |
| 289 | height: 14rpx; |
| 290 | margin: 0 6rpx; |
| 291 | border-radius: 20rpx; |
| 292 | transition: all 0.5s; |
| 293 | background-color: rgba(0, 0, 0, 0.3); |
| 294 | } |
| 295 | |
| 296 | .u-indicator-item-round-active { |
| 297 | width: 34rpx; |
| 298 | background-color: rgba(255, 255, 255, 0.8); |
| 299 | } |
| 300 | |
| 301 | .u-indicator-item-number { |
| 302 | padding: 6rpx 16rpx; |
| 303 | line-height: 1; |
| 304 | background-color: rgba(0, 0, 0, 0.3); |
| 305 | border-radius: 100rpx; |
| 306 | font-size: 26rpx; |
| 307 | color: rgba(255, 255, 255, 0.8); |
| 308 | } |
| 309 | |
| 310 | .u-list-scale { |
| 311 | transform-origin: center center; |
| 312 | } |
| 313 | |
| 314 | .u-list-image-wrap { |
| 315 | width: 100%; |
| 316 | height: 100%; |
| 317 | flex: 1; |
| 318 | transition: all 0.5s; |
| 319 | overflow: hidden; |
| 320 | box-sizing: content-box; |
| 321 | position: relative; |
| 322 | } |
| 323 | |
| 324 | .u-swiper-title { |
| 325 | position: absolute; |
| 326 | background-color: rgba(0, 0, 0, 0.3); |
| 327 | bottom: 0; |
| 328 | left: 0; |
| 329 | width: 100%; |
| 330 | font-size: 28rpx; |
| 331 | padding: 12rpx 24rpx; |
| 332 | color: rgba(255, 255, 255, 0.9); |
| 333 | } |
| 334 | |
| 335 | .u-swiper-item { |
| 336 | @include vue-flex; |
| 337 | overflow: hidden; |
| 338 | align-items: center; |
| 339 | } |
| 340 | </style> |