249 lines
6.0 KiB
Vue
249 lines
6.0 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([
|
||
'https://www.focusnu.com/media/directive/index/three/0.png',
|
||
'https://www.focusnu.com/media/directive/index/three/1.png',
|
||
'https://www.focusnu.com/media/directive/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>
|