249 lines
5.9 KiB
Vue
249 lines
5.9 KiB
Vue
|
<template>
|
|||
|
<view
|
|||
|
class="carousel"
|
|||
|
@touchstart="onTouchStart"
|
|||
|
@touchmove.prevent="onTouchMove"
|
|||
|
@touchend="onTouchEnd"
|
|||
|
>
|
|||
|
<view
|
|||
|
v-for="(img, i) in visibleImages"
|
|||
|
:key="img"
|
|||
|
class="carousel-item"
|
|||
|
:style="itemStyle(i)"
|
|||
|
>
|
|||
|
<image :src="img" mode="aspectFill" class="carousel-image" />
|
|||
|
<view class="font" :style="i==1?{fontWeight:600}:{}" >{{img.includes('0') ? imageName[0] : (img.includes('1') ? imageName[1] : imageName[2])}} </view>
|
|||
|
|
|||
|
</view>
|
|||
|
|
|||
|
<view
|
|||
|
class="carousel-item"
|
|||
|
:style="leftCopyStyle"
|
|||
|
key="left-copy"
|
|||
|
>
|
|||
|
<image :src="leftCopyImage" mode="aspectFill" class="carousel-image" />
|
|||
|
</view>
|
|||
|
|
|||
|
<view
|
|||
|
class="carousel-item"
|
|||
|
:style="rightCopyStyle"
|
|||
|
key="right-copy"
|
|||
|
>
|
|||
|
<image :src="rightCopyImage" mode="aspectFill" class="carousel-image" />
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup>
|
|||
|
import { ref, computed,watch } from 'vue';
|
|||
|
|
|||
|
const emit = defineEmits(['updateCenterIndex'])
|
|||
|
|
|||
|
|
|||
|
const images = ref([
|
|||
|
'/static/index/three/0.png',
|
|||
|
'/static/index/three/1.png',
|
|||
|
'/static/index/three/2.png',
|
|||
|
])
|
|||
|
const imageName = ref([
|
|||
|
"长者入住",
|
|||
|
"机构加盟",
|
|||
|
"员工入驻",
|
|||
|
])
|
|||
|
const len = images.value.length
|
|||
|
|
|||
|
const startX = ref(0)
|
|||
|
const position = ref(0)
|
|||
|
const isDragging = ref(false)
|
|||
|
const enableTransition = ref(false)
|
|||
|
const dragDirection = ref(0)
|
|||
|
const imageSpacing = 220
|
|||
|
|
|||
|
const centerIndex = computed(() => {
|
|||
|
return mod(Math.floor(position.value), len)
|
|||
|
})
|
|||
|
const leftIndex = computed(() => mod(centerIndex.value - 1, len))
|
|||
|
const rightIndex = computed(() => mod(centerIndex.value + 1, len))
|
|||
|
|
|||
|
const visibleImages = computed(() => [
|
|||
|
images.value[leftIndex.value],
|
|||
|
images.value[centerIndex.value],
|
|||
|
images.value[rightIndex.value],
|
|||
|
])
|
|||
|
|
|||
|
const leftCopyImage = computed(() => images.value[leftIndex.value])
|
|||
|
const rightCopyImage = computed(() => images.value[rightIndex.value])
|
|||
|
|
|||
|
function mod(n, m) {
|
|||
|
return ((n % m) + m) % m
|
|||
|
}
|
|||
|
|
|||
|
function onTouchStart(e) {
|
|||
|
if (enableTransition.value) enableTransition.value = false
|
|||
|
isDragging.value = true
|
|||
|
startX.value = e.touches[0].clientX
|
|||
|
dragDirection.value = 0
|
|||
|
}
|
|||
|
|
|||
|
function onTouchMove(e) {
|
|||
|
if (!isDragging.value) return
|
|||
|
const currentX = e.touches[0].clientX
|
|||
|
const delta = currentX - startX.value
|
|||
|
dragDirection.value = delta > 0 ? 1 : -1
|
|||
|
position.value -= delta / imageSpacing
|
|||
|
startX.value = currentX
|
|||
|
}
|
|||
|
|
|||
|
function onTouchEnd() {
|
|||
|
if (!isDragging.value) return
|
|||
|
isDragging.value = false
|
|||
|
enableTransition.value = true
|
|||
|
position.value = Math.round(position.value)
|
|||
|
}
|
|||
|
|
|||
|
function itemStyle(i) {
|
|||
|
const floorPos = Math.floor(position.value)
|
|||
|
const offset = position.value - floorPos // [-1, 1)
|
|||
|
const absOff = Math.abs(offset) // 0 → 1
|
|||
|
|
|||
|
// 计算水平偏移
|
|||
|
const baseX = (i - 1) * imageSpacing
|
|||
|
const translateX = baseX - offset * imageSpacing
|
|||
|
|
|||
|
// 透明度和 zIndex(保持原来逻辑不变)
|
|||
|
let opacity = 0.5
|
|||
|
let zIndex = 1
|
|||
|
if (i === 1) {
|
|||
|
opacity = 1 - 0.5 * absOff
|
|||
|
zIndex = 3
|
|||
|
} else if ((i === 2 && offset >= 0) || (i === 0 && offset < 0)) {
|
|||
|
opacity = 0.5 + 0.5 * absOff
|
|||
|
zIndex = 2
|
|||
|
}
|
|||
|
|
|||
|
// ← 这里是关键:统一用 absOff 计算 scale
|
|||
|
let scale = 0.8
|
|||
|
if (i === 1) {
|
|||
|
// 中心项目:offset 0 时放到 1.1,offset 1 时收到 0.8
|
|||
|
scale = 1.1 - 0.3 * absOff
|
|||
|
} else if ((i === 2 && offset >= 0) || (i === 0 && offset < 0)) {
|
|||
|
// 侧边项目:offset 0 时 0.8,offset 1 时 1.1
|
|||
|
scale = 0.8 + 0.3 * absOff
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
|
|||
|
opacity,
|
|||
|
zIndex,
|
|||
|
transition: enableTransition.value
|
|||
|
? 'transform 0.3s ease, opacity 0.3s ease'
|
|||
|
: 'none',
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const leftCopyStyle = computed(() => {
|
|||
|
const show = dragDirection.value === 1
|
|||
|
const floorPos = Math.floor(position.value)
|
|||
|
const offset = position.value - floorPos
|
|||
|
const translateX = 2.2 * imageSpacing - offset * imageSpacing
|
|||
|
|
|||
|
let scale = 0.8
|
|||
|
let opacity = 0.5
|
|||
|
let zIndex = 2
|
|||
|
|
|||
|
if (offset < 0) {
|
|||
|
const posOffset = -offset
|
|||
|
scale = 0.8 + 0.5 * posOffset
|
|||
|
opacity = 0.5 + 0.5 * posOffset
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
|
|||
|
opacity,
|
|||
|
zIndex,
|
|||
|
pointerEvents: 'none',
|
|||
|
position: 'absolute',
|
|||
|
top: '40%',
|
|||
|
left: '50%',
|
|||
|
opacity: show ? 0.5 : 0,
|
|||
|
willChange: 'transform, opacity',
|
|||
|
transition: enableTransition.value
|
|||
|
? 'transform 0.3s ease, opacity 0.3s ease'
|
|||
|
: 'none',
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
const rightCopyStyle = computed(() => {
|
|||
|
const show = dragDirection.value === 1
|
|||
|
const floorPos = Math.floor(position.value)
|
|||
|
const offset = position.value - floorPos
|
|||
|
const translateX = -2.2 * imageSpacing - offset * imageSpacing
|
|||
|
|
|||
|
let scale = 0.8
|
|||
|
let opacity = 0.5
|
|||
|
let zIndex = 2
|
|||
|
|
|||
|
if (offset >= 0) {
|
|||
|
scale = 0.8 + 0.5 * offset
|
|||
|
opacity = 0.5 + 0.5 * offset
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
|
|||
|
opacity,
|
|||
|
zIndex,
|
|||
|
pointerEvents: 'none',
|
|||
|
position: 'absolute',
|
|||
|
top: '40%',
|
|||
|
left: '50%',
|
|||
|
opacity: show ? 0.5 : 0,
|
|||
|
willChange: 'transform, opacity',
|
|||
|
transition: enableTransition.value
|
|||
|
? 'transform 0.3s ease, opacity 0.3s ease'
|
|||
|
: 'none',
|
|||
|
}
|
|||
|
})
|
|||
|
// 3. 监听 centerIndex 的变化,一旦改变就 emit
|
|||
|
watch(centerIndex, (newIdx) => {
|
|||
|
// console.log("???",newIdx)
|
|||
|
emit('updateCenterIndex', newIdx)
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
.carousel {
|
|||
|
position: relative;
|
|||
|
width: 90%;
|
|||
|
height: 650rpx;
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-item {
|
|||
|
position: absolute;
|
|||
|
width: 300rpx;
|
|||
|
height: 450rpx;
|
|||
|
top: 40%;
|
|||
|
left: 50%;
|
|||
|
margin: 0;
|
|||
|
backface-visibility: hidden;
|
|||
|
will-change: transform, opacity;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-image {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
border-radius: 12rpx;
|
|||
|
user-select: none;
|
|||
|
touch-action: pan-y;
|
|||
|
pointer-events: none;
|
|||
|
}
|
|||
|
.font{
|
|||
|
width: 100%;
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
margin-top: 20rpx;
|
|||
|
}
|
|||
|
</style>
|