首页 > 技术文章 > 小程序商品飞入购物车组件

Grani 2020-11-22 19:51 原文

wxml

<view class="fly-ball"
      hidden="{{ hidden }}"
      animation="{{ animation }}"
      style="top:{{ top }}px;left:{{ left }}px"></view>

wxss

.fly-ball {
    width: 30rpx;
    height: 30rpx;
    position: fixed;
    border-radius: 100%;
    z-index: 100;
    background: red;
    top: 200px;
    left: 10px;
    display: block;
    box-shadow: 0 1px 1px #000, 0 0 3px #fff inset;
}

JS

import { bezier } from "../../utils/util";

const app = getApp()

Component({
    /**
     * 组件的属性列表
     */
    properties: {},

    /**
     * 组件的初始数据
     */
    data: {
        loading: false,  // 动画是否进行中
        animation: {},
        hidden: true,
        top: 0,
        left: 0
    },

    /**
     * 组件的方法列表
     */
    methods: {

        // 动画
        ballAnimation(e) {

            // 节流
            if (this.data.loading) {
                return
            }

            let topPoint = {},
                finger = {},
                busPos = {
                    x: 250,
                    y: app.globalData.screenHeight
                }

            finger.x = e.touches[0].clientX  // 点击的位置
            finger.y = e.touches[0].clientY

            this.setData({
                top: finger.y,
                left: finger.x,
                hidden: false,
                loading: true
            })

            // 控制点y值定在低的点上方250px处
            if (finger.y < busPos.y) {
                topPoint.y = finger.y - 250
            } else {
                topPoint.y = busPos.y - 250
            }

            // 控制点确保x在点击点和购物车间
            if (finger.x > busPos.x) {
                topPoint.x = (finger.x - busPos.x) / 2 + busPos.x
            } else {
                topPoint.x = (busPos.x - finger.x) / 2 + finger.x
            }

            let linePos = bezier([busPos, topPoint, finger], 10),
                startPoint = linePos[linePos.length - 1]

            let animation = wx.createAnimation({
                duration: 40
            })

            for (let i = linePos.length - 1; i >= 0; i--) {

                // 根据坐标值逐个计算各点的translate
                let startPointX = startPoint.x,
                    startPointY = startPoint.y,
                    nextPointX = linePos[i].x,
                    nextPointY = linePos[i].y,
                    x = nextPointX - startPointX,
                    y = nextPointY - startPointY,
                    scale = 1

                if (i < 2) {
                    scale = 0.6
                }

                animation.translateX(x).translateY(y).scale(scale).step()
            }

            this.setData({
                animation: animation.export()
            })

            setTimeout(() => {

                this.setData({
                    loading: false,
                    animation: {
                        actions: [{
                            animates: [{ type: "translateX", args: [0] }, {
                                type: "translateY",
                                args: [0]
                            }, { type: "scale", args: [0, 0] }],
                            option: {
                                transition: { duration: 0 }
                            }
                        }]
                    }
                })
            }, 800)
        }
    }
})

二次贝塞尔曲线
util.js

function bezier(pots, amount) {
    let pot,
        lines,
        ret = [],
        points

    for (let i = 0; i <= amount; i++) {
        points = pots.slice(0)  // 购物车坐标、控制点坐标、点击坐标
        lines = []

        while (pot = points.shift()) {

            if (points.length) {
                lines.push(pointLine([pot, points[0]], i / amount))
            } else if (lines.length > 1) {
                points = lines
                lines = []
            } else {
                break
            }
        }

        ret.push(lines[0])
    }

    function pointLine(points, rate) {
        let pointA, pointB, pointDistance, xDistance, yDistance, tan, radian, tmpPointDistance
        let ret = []

        pointA = points[0]
        pointB = points[1]
        xDistance = pointB.x - pointA.x
        yDistance = pointB.y - pointA.y
        pointDistance = Math.pow(Math.pow(xDistance, 2) + Math.pow(yDistance, 2), 1 / 2)  // 斜边长
        tan = yDistance / xDistance
        radian = Math.atan(tan)
        tmpPointDistance = pointDistance * rate

        if (pointA.x < pointB.x) {
            ret = {
                x: pointA.x + tmpPointDistance * Math.cos(radian),
                y: pointA.y + tmpPointDistance * Math.sin(radian)
            }
        } else {
            ret = {
                x: pointA.x - tmpPointDistance * Math.cos(radian),
                y: pointA.y - tmpPointDistance * Math.sin(radian)
            }
        }

        return ret
    }

    return ret
}

调用

this.selectComponent("#flyBall").ballAnimation(e)

代码主要是抄考网上大神的,改动的地方只是把定时器换成了小程序支持的Animation并且可左右飞入
核心是二次贝塞尔曲线的计算,这个有机会要研究下...

学习资料

小程序购物车抛物线动画(通用)
贝塞尔曲线算法之JS获取点
贝塞尔曲线原理
贝塞尔曲线介绍及一阶、二阶推导

推荐阅读