鸿宇研学生前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

421 lines
10 KiB

<template>
<view class="sy-swiper" :style="{
backgroundColor: bgColor,
height: addUnit(height),
padding: padding,
borderRadius: addUnit(radius)
}">
<view v-if="showButton" class="sy-swiper__operation left" @click="slideLeft" :style="{
width: addUnit(buttonSize),
height: addUnit(buttonSize),
background: buttonBgColor,
fontSize: addUnit(buttonSize)
}">
<slot name="left-button"></slot>
</view>
<view class="sy-swiper__panel" @touchstart="onTouchStart" @touchend="onTouchEnd">
<view class="sy-swiper__panel-item" v-for="(item, index) in images" :key="index" :style="{
transform: itemStyles[index].transform,
zIndex: itemStyles[index].zIndex,
opacity: itemStyles[index].opacity,
transitionDuration: millisecondsToSeconds(duration) + 's',
transitionTimingFunction: easing
}">
<view class="sy-swiper__panel-item__content" @click="$emit('click', item, index)">
<template v-if="showFirstImageOnly">
<image class="slide" :src="item.url" :mode="imgMode" v-if="itemStyles[index].isTop" :style="{
borderRadius: addUnit(slideRadius)
}"></image>
<view class="slide-mask" v-else :style="{ background: maskBgColor, borderRadius: addUnit(slideRadius) }"></view>
</template>
<template v-else>
<image class="slide" :src="item.url" :mode="imgMode" :style="{ borderRadius: addUnit(slideRadius) }">
</image>
</template>
<text class="desc" :class="{ 'text': descNoWrap }" v-if="showDesc && item.desc" :style="{ background: descBgColor, color: descColor, fontSize: addUnit(descSize), borderRadius: `0 0 ${addUnit(slideRadius)} ${addUnit(slideRadius)}` }">{{ item.desc }}</text>
</view>
</view>
</view>
<view v-if="showButton" class="sy-swiper__operation right" @click="slideRight" :style="{
width: addUnit(buttonSize),
height: addUnit(buttonSize),
background: buttonBgColor,
fontSize: addUnit(buttonSize)
}">
<slot name="right-button"></slot>
</view>
</view>
</template>
<script>
import {
addUnit,
millisecondsToSeconds
} from "./util.js";
export default {
props: {
// 图片列表
images: {
type: Array,
default: () => []
},
padding: {
type: String,
default: "10px"
},
// 是否自动切换
autoplay: {
type: Boolean,
default: false
},
// 动画类型
easing: {
type: String,
default: "ease-in-out"
},
// 是否启用不透明度设定
enableOpacity: {
type: Boolean,
default: true
},
// 最顶层图片的不透明度
baseOpacity: {
type: Number,
default: 0.8
},
// 当前所在滑块的 index
current: {
type: Number,
default: 0
},
// 滑块自动切换时间间隔(ms)
interval: {
type: Number,
default: 2000
},
// 滑块切换过程所需时间(ms)
duration: {
type: Number,
default: 500
},
// 左右边距
horizontalMargin: {
type: [String, Number],
default: 10
},
// 上下边距
verticalMargin: {
type: [String, Number],
default: 10
},
// 组件高度
height: {
type: [String, Number],
default: 130
},
// 是否显示操作按钮
showButton: {
type: Boolean,
default: false
},
// 按钮字体大小
buttonSize: {
type: [String, Number],
default: 24
},
// 操作按钮背景颜色
buttonBgColor: {
type: String,
default: "rgba(0, 0, 0, 0.26)"
},
// 图片裁剪模式,详情见微信原生imageMode
imgMode: {
type: String,
default: "aspectFill"
},
// 是否只显示第一张图片,其他图片被蒙版遮住
showFirstImageOnly: {
type: Boolean,
default: false
},
// 遮住蒙版的颜色
maskBgColor: {
type: String,
default: "#ffffff"
},
// 是否显示图片描述(需要images传递的数据中存在desc属性)
showDesc: {
type: Boolean,
default: true,
},
// 底部描述的背景
descBgColor: {
type: String,
default: "rgba(0, 0, 0, 0.5)"
},
// 底部描述字体颜色
descColor: {
type: String,
default: "#ffffff"
},
// 底部描述字体大小
descSize: {
type: [Number, String],
default: 10
},
// 描述是否不换行,超出后用...省略
descNoWrap: {
type: Boolean,
default: false
},
// 轮播图片的圆角值
radius: {
type: [String, Number],
default: 4
},
// 容器背景色
bgColor: {
type: String,
default: "#ffffff"
},
slideRadius: {
type: [String, Number],
default: 10
}
},
// 切换
emits: ["click", "change"],
data() {
return {
slideNote: {
x: 0,
y: 0
},
itemStyles: [],
screenWidth: 0,
currentIndex: 0,
autoSlideInterval: null,
autoSlideTimeout: null
}
},
mounted() {
this.autoplay && this.doSomething();
},
onUnload() {
this.autoSlideInterval = null;
this.autoSlideTimeout = null;
},
beforeDestroy() {
clearInterval(this.autoSlideInterval);
clearTimeout(this.autoSlideTimeout);
this.autoSlideInterval = null;
this.autoSlideTimeout = null;
},
watch: {
images: {
handler(newImages) {
var macInfo = uni.getSystemInfoSync();
this.screenWidth = macInfo.screenWidth;
this.itemStyles = newImages.map((_, index) => (this.getStyle(index)));
},
deep: true,
immediate: true
},
current: {
handler(newCurrentIndex) {
this.scrollToCurrent(newCurrentIndex);
},
immediate: true
}
},
methods: {
addUnit,
millisecondsToSeconds,
doSomething() {
this.$nextTick(() => {
this.autoSlideInterval = setInterval(() => {
this.slideRight();
}, this.interval)
})
},
getStyle(eIndex) {
if (eIndex > this.images.length / 2) {
var right = this.images.length - eIndex;
return {
transform: "scale(" + (1 - right / this.verticalMargin) + ") translate(-" + right * this
.horizontalMargin + "%, 0px)",
zIndex: 100 - right,
opacity: this.enableOpacity ? this.baseOpacity / right : 1,
isTop: eIndex === 0
}
} else {
return {
transform: "scale(" + (1 - eIndex / this.verticalMargin) + ") translate(" + eIndex * this
.horizontalMargin + "%, 0px)",
zIndex: 100 - eIndex,
opacity: this.enableOpacity ? this.baseOpacity / eIndex : 1,
isTop: eIndex === 0
}
}
},
restartTimer() {
clearInterval(this.autoSlideInterval);
this.autoSlideInterval = null;
},
scrollToCurrent(currentIndex) {
// 清除现有的定时器,避免干扰
this.restartTimer();
// 这里选择用转动的方式,更改图片顺序会导致整个轮播的顺序错落
for (let i = 0; i < currentIndex; i++) {
this.slideRight();
}
},
simulateStartMove(x) {
this.slideNote.x = x;
},
onTouchStart(e) {
this.restartTimer();
this.slideNote.x = e.changedTouches[0] ? e.changedTouches[0].pageX : 0;
this.slideNote.y = e.changedTouches[0] ? e.changedTouches[0].pageY : 0;
},
onTouchEnd(e) {
var newList = JSON.parse(JSON.stringify(this.itemStyles));
if (e.changedTouches[0].pageX - this.slideNote.x < -10) {
// 向左滑动
var last = [newList.pop()];
newList = last.concat(newList);
this.currentIndex = (this.currentIndex + 1) % this.images.length;
} else if (e.changedTouches[0].pageX - this.slideNote.x >= 10) {
// 向右滑动
newList.push(newList[0]);
newList.splice(0, 1);
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
}
this.itemStyles = newList;
if (this.autoplay) {
clearInterval(this.autoSlideInterval);
clearTimeout(this.autoSlideTimeout);
// 设置新的定时器,使用 this.interval 值作为间隔时间
this.autoSlideTimeout = setTimeout(() => {
this.slideRight();
}, this.interval);
}
},
slideLeft() {
this.restartTimer();
this.simulateStartMove(this.slideNote.x); // 模拟 touchstart,确保 slideNote.x 更新
this.onTouchEnd({
changedTouches: [{
pageX: this.slideNote.x + 20
}]
});
// 触发 change 事件并传递当前的索引
this.$emit('change', this.currentIndex);
},
slideRight() {
this.restartTimer();
this.simulateStartMove(this.slideNote.x); // 模拟 touchstart,确保 slideNote.x 更新
this.onTouchEnd({
changedTouches: [{
pageX: this.slideNote.x - 20
}]
});
// 触发change 事件并传递当前的索引
this.$emit('change', this.currentIndex);
}
}
}
</script>
<style lang="scss" scoped>
.sy-swiper {
position: relative;
box-sizing: border-box;
.sy-swiper__operation {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 50%;
background: rgba(0, 0, 0, 0.26);
z-index: 999;
&:active {
filter: brightness(70%);
}
}
.sy-swiper__operation.left {
top: 50%;
left: 10px;
transform: translateY(-50%);
}
.sy-swiper__operation.right {
bottom: 50%;
right: 10px;
transform: translateY(50%);
}
.sy-swiper__panel {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.sy-swiper__panel-item {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
transition-property: transform, opacity;
// transition-duration: 0.4s;
// transition-timing-function: ease-in-out;
&__content {
height: 100%;
width: 330rpx;
margin: 2rpx auto;
position: relative;
.slide {
height: 100%;
width: 100%;
}
.slide-mask {
position: relative;
height: 100%;
width: 100%;
z-index: 1;
}
.desc {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
padding: 10rpx 20rpx;
box-sizing: border-box;
box-shadow: 0rpx 4rpx 21rpx 0rpx rgba(0, 0, 0, 0.07);
// border-radius: 0 0 20px 20px;
font-family: PingFang SC;
font-weight: 400;
line-height: 32rpx;
text-transform: none;
}
}
}
}
}
.text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>