原生小程序写卡片堆叠式轮播图

首先看下最终的效果,三张卡片堆叠式swiper,居中的为展示,左右两边为前一个和后一个,如果是第一长,或者最后一张,对应的前后无阴影堆叠,可进行跟手切换

RPReplay.gif

实现思路

一共渲染出3个卡片,然后根据显示位置设置active,prev,next等属性,监听用户的滑动行为,对3个卡片的位置进行调整,然后只在touchMove时进行切换

代码如下

WXML:

<!-- pages/homeTest/homeTest.wxml -->
<view class="container" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd">

    <view class="swiper-item {{currentIndex == item.id ? 'activeItem' : ''}} {{prevIndex == item.id ? 'prevItem' : ''}} {{nextIndex == item.id ? 'nextItem' : ''}}" 
    style="{{currentIndex == item.id ? activeAnimation : prevIndex == item.id ? prevAnimation : nextIndex == item.id ? nextAnimation : ''}}"  
    wx:for="{{imgUrls}}" wx:index="{{index}}" wx:key="id">
      <image mode="aspectFill" src="{{item.image}}" class="slide-image" />

    </view>
 
</view>

<view class="dots-box own-class">
  <view class="dots {{currentIndex == index ? 'bg-333' : ''}}" wx:for="{{imgUrls}}" wx:key="index"></view>
</view>

WXSS:

page {
    background-color: #fff;
}

.container {
    display: flex;
    flex-direction: row;
    justify-content: center;
    position: relative;
    margin-top: 200rpx;
   
}



.swiper-item {
    position: absolute;
    display: flex;
    box-sizing: border-box;
    z-index: 1;
    display: none;
    
}



.slide-image {
    width: 617rpx;
    height: 636rpx;
    border-radius: 16rpx;
    z-index: 1;
    box-shadow: 10rpx 5rpx 40rpx rgba(0, 0, 0, 0.5);
   
}

/* 上一个 */
.prevItem {
    left: 17rpx;
    transform: scale(1) ;
    display: block;
    opacity: 0.4;
}

.nextItem {
    right: 17rpx;
    transform:scale(1) ;
    display: block;
    opacity: 0.4;


}

.activeItem {
    position: absolute;
    transform: scale(1.1);
    display: block;
    z-index: 3;
}


.dots-box {
    display: flex;
    justify-content: start;
    align-items: center;
    padding-left: 32rpx;
}

.dots {
    width: 20rpx;
    height: 8rpx;
    margin: 0 4rpx;
    background: #D9D9D9;
    border-radius: 4px;
    margin-top: 170rpx;
  
}

.bg-333 {
    background-color: #A9B3FA;
}

JS:

// pages/homeTest/homeTest.js
Page({

    /**
     * 页面的初始数据
     */
    data: {
        // 图片数组
        prevIndex: 0,
        currentIndex: -1,
        nextIndex: 0,
        activeAnimation: '',
        prevAnimation: '',
        nextAnimation: '',
        imgUrls: [ // 卡片数据
            {
                id: 0,
                title: "Card 1",
                image: "https://qiniusleep.25youpin.com/bgImages/202304211420346150.png"
            },
            {
                id: 1,
                title: "Card 2",
                image: "https://qiniusleep.25youpin.com/bgImages/202304211420304869.png"
            },
            {
                id: 2,
                title: "Card 3",
                image: "https://qiniusleep.25youpin.com/bgImages/202304211419587459.png"
            },
            // {
            //     id: 3,
            //     title: "Card 4",
            //     image: "https://qiniusleep.25youpin.com/bgImages/202304211420329910.png"
            // },
            // {
            //     id: 4,
            //     title: "Card 5",
            //     image: "https://qiniusleep.25youpin.com/bgImages/202304141656539615.png"
            // },
        ],
        startX: 0,
        // 移动方向
        direction: '',
        //开始动画
        startAnimate:false
    },

    /**
     * 生命周期函数--监听页面加载
     */
    onLoad(options) {

        this.initIndex()

    },
    initIndex() {
        this.swiperChange({
            detail: {
                current: 0
            }
        })
    },
    async swiperChange(e) {
        let currentIndex = e.detail.current; // 获取当前swiper-item的index
        let prevIndex = currentIndex > 0 ? currentIndex - 1 : this.data.imgUrls.length - 1; // 获取前一个swiper-item的index
        let nextIndex = currentIndex < this.data.imgUrls.length - 1 ? currentIndex + 1 : 0; // 获取后一个swiper-item的index
        this.setData({
            currentIndex: e.detail.current,
            prevIndex: prevIndex,
            nextIndex: nextIndex,
        });
    },
    //touchstart 事件处理函数
    touchStart(e) {
        // 记录当前手指触摸屏幕时的坐标
        this.setData({
            startX: e.touches[0].pageX
        });
    },

    // touchmove 事件处理函数
    touchMove(e) {
        if(this.data.startAnimate)return
        // 获取当前手指的坐标
        let currentX = e.touches[0].pageX;
        // 计算手指的滑动方向
        let progress = ((Math.abs(currentX - this.data.startX)) / 350).toFixed(2);
        let direction = currentX - this.data.startX > 0 ? 'right' : 'left';
        if (currentX - this.data.startX == 0) return

        if (progress > 1) progress = 1
        // 根据滑动方向执行相应的操作

        // 设置元素的位置和大小
        let scale = progress * 0.1;
        let threshold = 0.15 //切换的阈值
        let opacity = 0.6 * (progress * (1 / threshold) > 1 ? "1" : progress * (1 / threshold));


        if (direction === 'right') {

            let activeStyle = `transform:scale(${1.1-scale}) translateX(${progress*50}rpx);opacity:${1-opacity};z-index:${progress>threshold?2:3};transition:all 0.1s`
            let prevStyle = `transform:scale(${1+scale}) translateX(${progress*50}rpx);opacity:${0.4+opacity};z-index:${progress>threshold?3:2};transition:all 0.1s`
            let nextStyle = `transform: translateX(${-progress*100}rpx);z-index:1;transition:all 0.1s`
            this.setData({
                activeAnimation: activeStyle,
                prevAnimation: prevStyle,
                nextAnimation: nextStyle,
                direction: 'right',
                progress: progress,

            })
            // 执行向右滑动的操作
        } else if (direction === 'left') {
            // 执行向左滑动的操作

            let activeStyle = `transform:scale(${1.1-scale}) translateX(${-progress*50}rpx);opacity:${1-opacity};z-index:${progress>threshold?2:3};transition:all 0.1s`
            let nextStyle = `transform:scale(${1+scale}) translateX(${-progress*50}rpx);opacity:${0.4+opacity};z-index:${progress>threshold?3:2};transition:all 0.1s`
            let prevStyle = `transform: translateX(${progress*100}rpx);z-index:1;transition:all 0.1s`
            this.setData({
                activeAnimation: activeStyle,
                prevAnimation: prevStyle,
                nextAnimation: nextStyle,
                direction: 'left',
                progress: progress,

            })
        }
    },
    touchEnd() {
       
        let threshold = 0.15 //切换的阈值
        if (this.data.progress < threshold) {
            this.setData({
                activeAnimation: 'transition:all 0.4s;',
                prevAnimation: 'transition:all 0.4s;',
                nextAnimation: 'transition:all 0.4s;',
                direction: '',
                startAnimate:true,
            })
        } else {

            if (this.data.direction == "left") {
                let activeStyle = `transform:scale(1) translateX(-50rpx);opacity:0.4;z-index:2;transition:all 0.3s;`
                let nextStyle = `transform:scale(1.1) translateX(-47rpx);opacity:1;z-index:3;transition:all 0.3s;`
                let prevStyle = `transform:scale(1) translateX(100rpx);opacity:0.4;z-index:1;transition:all 0.3s;`
                this.setData({
                    activeAnimation: activeStyle,
                    prevAnimation: prevStyle,
                    nextAnimation: nextStyle,
                    direction: 'left',
                    startAnimate:true,
                })
            } else if (this.data.direction === 'right') {
                let activeStyle = `transform:scale(1) translateX(50rpx);opacity:0.4;z-index:2;transition:all 0.3s;`
                let prevStyle = `transform:scale(1.1) translateX(47rpx);opacity:1;z-index:3;transition:all 0.3s;`
                let nextStyle = `transform:scale(1) translateX(-100rpx);opacity:0.4;z-index:1;transition:all 0.3s;`
                this.setData({
                    activeAnimation: activeStyle,
                    prevAnimation: prevStyle,
                    nextAnimation: nextStyle,
                    direction: 'right',
                    startAnimate:true,
                })
            }
        }
        setTimeout(() => {

            this.setData({
                activeAnimation: '',
                prevAnimation: "",
                nextAnimation: "",
                progress: 0,
                startAnimate:false,
            })
            this.animateEndchangeSwiper()

        }, 300);

    },
    animateEndchangeSwiper() {
        if (this.data.direction == 'right') {
            this.swiperChange({

                detail: {
                    current: this.data.prevIndex
                }

            })

        } else if (this.data.direction == 'left') {
            this.swiperChange({
                detail: {
                    current: this.data.nextIndex
                }

            })
            
        }

        this.setData({
            direction: '',
           
        })
    },
    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady() {

    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {

    },

    /**
     * 生命周期函数--监听页面隐藏
     */
    onHide() {

    },

    /**
     * 生命周期函数--监听页面卸载
     */
    onUnload() {

    },

    /**
     * 页面相关事件处理函数--监听用户下拉动作
     */
    onPullDownRefresh() {

    },

    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom() {

    },

    /**
     * 用户点击右上角分享
     */
    onShareAppMessage() {

    }
})

代码世界的构建师,现实生活的悠游者。