小程序的知识---微信朋友圈选图效果@微信开放社区里的卢霄霄

找到工作了,我第一次接手小程序,现在就想把我遇到的一些问题记录一下。

需求:仿造的微信朋友圈选图,长按移动排序主要是用交互监听来实现的。

@微信开放社区里的卢霄霄代码片段:wechatide://minicode/RFLmzDmL7I2a

@微信开放社区里的卢霄霄新的片段:wechatide://minicode/fOTEM3mI7l3B

代码如下:

//index.wxml

<view class="image-container">
  <view class="seat-panel">
    <view wx:for="{{seatNumber}}" wx:key="*this" class="seats">
      <view class="inner" id="inner{{index}}"></view>
    </view>
  </view>
  <view class="image-panel">
    <image wx:for="{{imageArray}}" wx:key="*this" mode="aspectFill" src="{{item}}" class="images" animation="{{animationArray[index]}}" bindtouchstart='start' bindtouchmove='move' bindtouchend='end' data-index="{{index}}"></image>
    <view class="add-image" bindtap="addImage" animation="{{animationAdd}}" wx:if="{{imageArray.length<9}}">+</view>
  </view>
  <image id="movingCover" mode="aspectFill" src="{{selectedImg}}" class="cover-images" animation="{{animationMove}}"></image>
</view>
<button bindtap="getResult" class="result-button" type="primary">获取结果</button>
<view id="del" class="delete-area" animation="{{animationDel}}">拖到这里删除</view>

 

//index.wxss
page {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.image-container {
  width: 700rpx;
  height: 700rpx;
  position: relative;
  z-index: 1;
}

.seats {
  width: 200rpx;
  height: 200rpx;
  padding: 15rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.add-image {
  width: 200rpx;
  height: 200rpx;
  margin: 13rpx;
  border: 1rpx solid black;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: gray;
}

.images {
  width: 200rpx;
  height: 200rpx;
  padding: 15rpx;
  position: absolute;
}

.cover-images {
  width: 200rpx;
  height: 200rpx;
  margin: 15rpx;
  position: absolute;
  z-index: 1;
  pointer-events: none;
}

.inner {
  width: 30rpx;
  height: 30rpx;
}

.seat-panel {
  position: absolute;
  display: flex;
  flex-wrap: wrap;
  z-index: 100;
  pointer-events: none;
}

.image-panel {
  position: absolute;
  z-index: 0;
}

.result-button {
  width: 80%;
}

.delete-area {
  background-color: red;
  position: fixed;
  bottom: -50px;
  height: 50px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  z-index: 0;
}
//index.js
/**
 * 整体概述
 *
 *  最自然的想法是把相交事件触发写到每个图片上,然后长按图片的时候,可以拖动当前图片,当前图片会和其他图片有个相交状态。
 *  这样做的话,有4个比较难处理的点:
 *    1、当前照片的 z-index 的设置,要让他高度为最高。 z-index 和 transform 有个失效的问题。(不是说不能处理哈,只是说有这个问题)
 *    2、移动动画中的图片和当前图片的相交状态的触发(要写一些条件判断,而且很难做成连续触发)
 *    3、图片两两之间需要有个相交监听,另外每张图都要和删除区域有个相交监听。
 *    4、空白占位的处理及各个位置的计算。
 *  基于以上考虑,改成了由1个公共的移动块,每个位置有个座位,座位正中间放个小块,来触发相交事件。给这个移动块设上当前移动的图片的src。现结构如下:
 *  一共分为4个部分,座位层、图片层、移动块、删除区域。
 *  座位层:相当于是底座,用flex布局,然后用createSelectorQuery取得每个座位的坐标,再把图片层的每个图片设置到对应位置。因为有个添加按钮,所以座位会比图片的列表多1
 *  图片层:就是可见的图片区域
 *  移动块:用来触发各种区域相交事件的块。
 *  删除区域:底部的提示删除部分,当移动块有移动的时候才显示,拖上去后会把 isDelete = true,touchend的时候进行删除。
 *
 */
// 记录每个座位的位置,方便悬浮块定位,以及每次动画,各块的位移,存储单位为px,[left,top]
let seatsArray = []
// 记录当前每个图当前位于哪个座位。比如最开始添加了3张图,那么positionArray里就应该是[0,1,2]。如果位置2的移动到0的位置,那么数组就变成了[2,0,1]
let positionArray = []
// observer列表,方便删除的时候销毁
let observerArray = []
// 动画实例列表
let animationArray = []
// px和rpx的转换率 1px * rate = 1rpx
let rate = 1
// 移动块的动画实例
let animationMove = wx.createAnimation({})
// 新增按钮的动画实例
let animationAdd = wx.createAnimation({})
// 删除区域的动画实例
let animationDel = wx.createAnimation({})

Page({
    data: {

        // 图片层部分的动画列表,新增按钮除外
        animationArray: [],

        // 移动块的动画
        animationMove: {},

        // 新增按钮的动画
        animationAdd: {},

        // 删除区域的动画
        animationDel: {},

        // 图片的列表,在获取结果前,顺序固定,不会跟着界面上的可视位置改变
        imageArray: [],

        // 座位个数,因为要计算新增按钮位置,所以座位个数是图片数量+1
        // TODO 这里有个问题没处理,就是选了9张图后,就有了10个位置。。界面上会多出一行的位置。
        seatNumber: 0,

        // 当前选中块在 imageArray中的位置
        selected: -1,
    },
    onLoad: function() {
        let ret = wx.getSystemInfoSync()
        rate = 750 / ret.windowWidth // 750rpx除以当前屏幕宽度的px值,得到转换比

        // 是否显示删除区域
        this.showDel = false
    },
    onReady: function() {
        // 监听移动块和删除区域的相交
        let _observer = wx.createIntersectionObserver()
        _observer
            .relativeTo('#movingCover')
            .observe('#del', (res) => {
                if (res.intersectionRatio == 0) {
                    this.delete = false
                } else {
                    this.delete = true
                }
            })
    },
    /**
     * 触摸开始,要记录开始点的位置,记录当前按中的图的序号
     */
    start(e) {
        this.delete = false
        this.data.selected = e.currentTarget.dataset.index
        this.startX = e.changedTouches[0].pageX
        this.startY = e.changedTouches[0].pageY

        // 先把移动块位置移动到当前选中的图的位置
        // 当前选中图的座位坐标
        let seat = seatsArray[positionArray[this.data.selected]]

        this.selectedPos = seat
        animationMove.translate(seat[0], seat[1]).step({
            duration: 10,
            timingFunction: 'step-end'
        })
        this.setData({
            animationMove: animationMove.export(),
        })
        this.focusTimer = setTimeout(() => {
            // 把移动块进行放大,透明
            animationMove.scale(1.1).opacity(0.9).step({
                duration: 200
            })
            // 同时隐藏掉本来这个位置的图
            animationArray[this.data.selected].opacity(0).step({
                duration: 10,
                timingFunction: 'step-end'
            })
            this.data.animationArray[this.data.selected] = animationArray[this.data.selected].export()
            this.setData({
                selectedImg: this.data.imageArray[this.data.selected],
                selected: this.data.selected,
                animationMove: animationMove.export(),
                animationArray: this.data.animationArray
            }, () => {
                this.startMove = true
            })
        }, 200)
    },

    /**
     * 触摸移动
     */
    move(e) {
        // 表明已经move了,就不再end的时候不再进行预览
        this.trigger = true

        if (this.focusTimer) {
            clearTimeout(this.focusTimer)
            this.focusTimer = 0
        }
        if (!this.startMove) {
            return
        }

        // 移动的时候,如果没显示底部删除区域,则显示底部删除区域
        if (!this.showDel) {
            animationDel.translateY(-50).step()
            this.setData({
                animationDel: animationDel.export()
            })
            this.showDel = true
        }

        let startX = e.changedTouches[0].pageX
        let startY = e.changedTouches[0].pageY
        let positionX = this.selectedPos[0] + startX - this.startX
        let positionY = this.selectedPos[1] + startY - this.startY

        // 设置移动块当前位置
        animationMove.translate(positionX, positionY).step({
            timingFunction: 'step-start'
        })
        this.setData({
            animationMove: animationMove.export(),
        })
    },
    /**
     * 触摸结束,如果是短按,切没移动过,就预览图片。
     */
    end(e) {
        if (this.focusTimer) {
            clearTimeout(this.focusTimer)
            this.focusTimer = 0
        }
        if (!this.startMove) {
            if (!this.trigger) {
                wx.previewImage({
                    urls: [this.data.imageArray[this.data.selected]]
                })
            }
            return
        }
        this.startMove = false
        this.trigger = false
        if (this.showDel) {
            animationDel.translateY(0).step()
            this.setData({
                animationDel: animationDel.export()
            })
            this.showDel = false
            if (this.delete) {
                this.deleteSelected()
                return
            }
        }

        // 将移动块放回之前选中的图现在所在的位置
        let seat = seatsArray[positionArray[this.data.selected]]
        animationMove.translate(seat[0], seat[1]).scale(1).opacity(1).step({
            duration: 100
        })
        // 在移动块回来后,显示在touchstart时候隐藏掉的,当前选中的这个图,设200的delay是确保之前100ms的动画执行完
        animationArray[this.data.selected].opacity(1).step({
            delay: 200,
            duration: 10,
        })
        this.data.animationArray[this.data.selected] = animationArray[this.data.selected].export()
        this.setData({
            animationMove: animationMove.export(),
            animationArray: this.data.animationArray
        }, () => {
            // 确保touchstart时候隐藏掉的,当前选中的这个图已经显示,延迟隐藏移动块
            setTimeout(() => {
                this.setData({
                    selectedImg: "",
                    selected: -1,
                })
            }, 300)

        })
    },

    /**
     * 相交事件触发时候,整体的移动
     * isFront true为往前移动,false为往后移动
     * startPos 需要移动的起始位置
     * endPos 需要移动的重点位置
     */
    _moveImages(isFront, startPos, endPos) {
        let length = this.data.imageArray.length
        for (let i = 0; i < length; i++) {
            let targetPos
            let position = positionArray[i]
            if (isFront) {
                // 往前移动的时候,选中图当前位置到选中图本来位置之间的图,均向后移动一格
                if (position >= startPos && position < endPos) {
                    targetPos = position + 1
                    positionArray[i] = targetPos
                    animationArray[i].translate(seatsArray[targetPos][0], seatsArray[targetPos][1]).step()
                    this.data.animationArray[i] = animationArray[i].export()
                }
            } else {
                // 往后移动的时候,选中图当前位置到选中图本来位置之间的图,均向前移动一格
                if (position > startPos && position <= endPos) {
                    targetPos = position - 1
                    positionArray[i] = targetPos
                    animationArray[i].translate(seatsArray[targetPos][0], seatsArray[targetPos][1]).step()
                    this.data.animationArray[i] = animationArray[i].export()
                }
            }
        }

        // 移动当前选中图的位置,往前的时候,移动到起点位置;往后的时候,移动到终点位置
        if (isFront) {
            positionArray[this.data.selected] = startPos
            animationArray[this.data.selected].translate(seatsArray[startPos][0], seatsArray[startPos][1]).step()
        } else {
            positionArray[this.data.selected] = endPos
            animationArray[this.data.selected].translate(seatsArray[endPos][0], seatsArray[endPos][1]).step()
        }
        this.data.animationArray[this.data.selected] = animationArray[this.data.selected].export()
        this.setData({
            animationArray: this.data.animationArray
        })
    },

    /**
     * 添加图片
     */
    addImage() {
        wx.chooseImage({
            count: 9 - this.data.imageArray.length, //最多9张图
            success: (res) => {
                let length = this.data.imageArray.length
                // TODO 这里本来不必和删除的时候一样整理整个图片列表的,但是有个顺序问题,一直没处理好。。
                let newImageArray = []
                for (let i = 0; i < length; i++) {
                    let position = positionArray[i]
                    newImageArray[position] = this.data.imageArray[i]
                }
                newImageArray = newImageArray.concat(res.tempFilePaths)
                observerArray.forEach(item => {
                    item.disconnect()
                })

                this.setData({
                    imageArray: newImageArray,
                    seatNumber: newImageArray.length + 1
                }, () => {
                    this._addObservers()
                    this._getSeats()
                })
            },
        })
    },
    /**
     * 删除当前选中的图
     */
    deleteSelected() {
        for (let i = 0; i < animationArray.length; i++) {
            animationArray[i].opacity(1).step({
                duration: 10,
                timingFunction: 'step-end'
            })
            this.data.animationArray[i] = animationArray[i].export()
        }

        this.setData({
            selectedImg: "",
            animationArray: this.data.animationArray
        }, () => {
            let selectedPos = positionArray[this.data.selected]
            let length = this.data.imageArray.length
            let newImageArray = []
            for (let i = 0; i < length; i++) {
                let position = positionArray[i]
                if (position < selectedPos) {
                    newImageArray[position] = this.data.imageArray[i]
                } else if (position > selectedPos) {
                    newImageArray[position - 1] = this.data.imageArray[i]
                }
            }
            observerArray.forEach(item => {
                item.disconnect()
            })
            this.setData({
                imageArray: newImageArray,
                seatNumber: newImageArray.length + 1
            }, () => {

                this._addObservers()
                this._getSeats()

                // 复位移动块状态
                animationMove.scale(1).opacity(1).step({
                    duration: 10,
                    timingFunction: 'step-end'
                })
                this.setData({
                    animationMove: animationMove.export()
                }, () => {
                    this.setData({
                        selected: -1,
                    })
                })
            })
        })
    },

    /**
     * 获取最后的图片列表
     */
    getResult() {
        let length = this.data.imageArray.length
        let newImageArray = []
        for (let i = 0; i < length; i++) {
            let position = positionArray[i]
            newImageArray[position] = this.data.imageArray[i]
        }
        console.log(newImageArray)
    },

    /**
     * 添加相交监听
     */
    _addObservers: function() {
        let length = this.data.imageArray.length
        for (let i = 0; i < length; i++) {
            let _observer = wx.createIntersectionObserver()
            _observer
                .relativeTo('#movingCover')
                .observe('#inner' + i, (res) => {
                    if (res.intersectionRatio > 0 && res.intersectionRatio < 1) {
                        let currentPos = positionArray[this.data.selected]
                        if (i < currentPos) {
                            // 往前挪,i位置到currentPos之间的所有块都向后挪1格,current == i
                            this._moveImages(true, i, currentPos)
                        } else if (i > currentPos) {
                            // 往后挪,i位置到currentPos之间的所有块都向前挪1格,current == i
                            this._moveImages(false, currentPos, i)
                        }
                    }
                })
            observerArray[i] = _observer
            positionArray[i] = i
        }
    },

    /**
     * 获取所有座位位置
     */
    _getSeats: function() {
        wx.createSelectorQuery().selectAll('.seats').boundingClientRect(res => {
            let drift = [res[0].left, res[0].top]
            console.log(res)
            seatsArray = []
            res.forEach(item => {
                let seat = [item.left - drift[0], item.top - drift[1]]
                seatsArray.push(seat)
            })
            let length = res.length - 1
            animationArray = []
            this.data.animationArray = []
            for (let i = 0; i < length; i++) {
                let seat = [res[i].left - drift[0], res[i].top - drift[1]]
                seatsArray[i] = seat
                let animation = wx.createAnimation({
                    timingFunction: 'easy-out'
                })
                animationArray[i] = animation
                animation.translate(seat[0], seat[1]).step({
                    duration: 100,
                    timingFunction: 'step-end'
                })
                this.data.animationArray[i] = animation.export()
            }
            animationAdd.translate(res[length].left - drift[0], res[length].top - drift[1]).step({
                duration: 100,
                timingFunction: 'step-end'
            })
            this.setData({
                animationArray: this.data.animationArray,
                animationAdd: animationAdd.export()
            })
        }).exec()
    }
})

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值