🛒🚀🎉前端开发,实现商品抛物线飞入购物车

9,132 阅读5分钟

image.png

Kapture 2024-11-12 at 15.22.40.gif

一、运动动画

抛物线由两段运动构成:
1、
2、

忽略空气阻力。
横向是匀速直线运动,
纵向是自由落体运动。

用css做

一个元素不同方向动画

<html lang="en">
<head>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        .ball {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: #B30204;
        }
    </style>
</head>

<body>
    <div class="ball"></div>
</body>
</html>

给这个小球动画,分别一个x方向的移动,一个y方向的移动。

// x
@keyframes moveX {
    to {
        transform: translateX(200px);
    }
}

// y
@keyframes moveY {
    to {
        transform: translateY(400px);
    }
}

.ball {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background: #B30204;
    animation: moveX 2s linear forwards, moveY 2s ease forwards;
}

看看效果:

Kapture 2024-11-12 at 16.00.29.gif

不是我们想要的效果。为什么呢?是因为一个元素同一时间,赋予两个动画,既让它往左走200米,又让它走往下走400米。明显造成冲突了。

那么如何来解决这个问题呢?多元素动画。每一个负责一段运动就好了,那么就不存在css属性冲突这个问题了。

多元素各负责一个动画

<html lang="en">
<head>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        .ball {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: #B30204;
            animation: moveX 2s linear forwards;
        }
        .inner {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: green;
            animation: moveY 2s ease forwards;
        }
        
        // 动画
        // x
        @keyframes moveX {
            to {
                transform: translateX(200px);
            }
        }

        // y
        @keyframes moveY {
            to {
                transform: translateY(400px);
            }
        }
    </style>
</head>

<body>
    <div class="ball">
        <div class="inner"></div>
    </div>
</body>
</html>

现在有两个元素了。一个x轴向右匀速移动200px,一个y轴向下缓慢加速移动400px。我们来看看效果:

Kapture 2024-11-12 at 16.41.39.gif

🔴 红色是父
🟢 绿色是子
终点(x, y)是(200, 400)

image.png

x轴方向的父确确实实也会影响y轴方向的子,只不过这个效果没达到我们想要的那种抛物线的效果。

贝塞尔曲线

那么,想要达到我们想要的效果,y轴方向,其实是需要先向上抛一段路程,再向下做加速运动的。css动画中能满足我们随心所欲地定制复杂动画的需求,这里就需要用到贝塞尔曲线cubic-bezier()函数

基本语法

cubic-bezier(x1, y1, x2, y2)
x1、y1、x2、y2这四个参的取值范围都是(0到1),较小就较慢,较大就较快。

参数含义

x1, y1 :起始速度(水平,垂直)

  • x1:控制动画起始点的水平方向速度变化。

  • y1:控制动画起始点的垂直方向速度变化。

x2, y2 :结束速度(水平,垂直)

  • x2:控制动画结束点的水平方向速度变化。

  • y2:控制动画结束点的垂直方向速度变化。

我们平常碰到的easeease-inease-outease-in-out等等这些,实际上就是贝塞尔曲线cubic-bezier()的特定实例。

实际上讲的是速度。只要x1=y1, x2=y2那即是匀速linear

image.png


贝塞尔曲线这点回到抛物线这里。

要想做抛物线,就得知道这条线是先远离目标值,再逼近目标值。那么我们可以设置它的时间函数animation-timing-functioncubic-bezier(0.5, -0.5, 1, 1)

1.gif

二、实战

按钮(点击加号飞向购物车,实际上我们这案例是点加号,加号自己根据自己的位置飞向购物车,一般呢,这是不用选规格的商品就这样做,要选规格的,就跳到选这个商品的规格详情界面再去加,啰嗦了,大家去小程序上点个外卖瞅一眼)、加号购物车

<html lang="en">
    <head>
        <style>
            // 加号位置动态变,方便后面用js控制它的位置
            .plus {
                left: var(--left);
                top: var(--top);
                animation: moveY 0.8s cubic-bezier(0.5, -0.5, 1, 1);
            }
            
            .plus .iconfont {
                animation: moveX 0.8s linear;
            }
            
            @keyframes moveY {
                to {
                    transform: translateY(var(--y));
                }
            }
            
            @keyframes moveX {
                to {
                    transform: translateX(var(--x));
                }
            }
        </style>
    </head>
    
    <body>
        <div class="cart"></div> // 购物车
        <button class="btn">点击</button> // 点击按钮
        
        // 这个静态固定位置的(动态时需注释掉,需动态添加),下面动态js代码如下👇
        <div class="plus" style="--left: 300px; --top: 300px; --y: 400px; --x: 200px;"> // 加号
            <i class="iconfont"></i>
        </div>
    </body>
</html>

以上是静态加号抛物线加入购物车。现在动态做,js去添加号,动态主要是要去算元素的位置,而不是固定的位置。

// 根据加号的父元素的位置,去设置加号的位置

/**
* 1、实现一个点击按钮,在按钮位置生成一个加号。
* 2、通过动画效果将加号抛物线姿势飞向购物车,飞完后移除该加号。
* 3、动态设置--left、--top、--x和--y去控制加号的位置和移动路径。
*/
const cart = document.querySelector(".cart");
const btn = document.querySelector(".btn");
const PLUS_SIZE  = 30; // 加号的宽度

btn.onclick = function () {
  const div = document.createElement("div");
  div.className = "plus";
  div.innerHTML = `<i class="iconfont"></i>`;

  const btnRect = btn.getBoundingClientRect();
  const left = btnRect.left + btnRect.width / 2 - PLUS_SIZE / 2,
      top = btnRect.top + btnRect.height / 2 - PLUS_SIZE / 2;

  // 横向移动的距离是 加号的left加上它自身宽度的一半,减去购物车的left加上它自身宽度的一半。
  const catRect = cart.getBoundingClientRect();
  const x = (catRect.left + catRect.width / 2) - PlUS_SIZE/2-left;
  // 纵向移动的距离是 加号的top加上它自身高度的一半,减去购物车的top加上它自身高度的一半。
  const y = (catRect.top + catRect.height / 2) - PLUS_SIZE/2-top;

  div.style.setProperty("--left", `${left}px`);
  div.style.setProperty("--top", `${top}px`);
  div.style.setProperty("--x", `${x}px`);
  div.style.setProperty("--y", `${y}px`);

  div.addEventListener("animationend", () => { div.remove(); }) // 飞完后消失

  document.body.appendChild(div);
}

css

.plus {
  --duration: 0.5s; // 定义自定义属性--duration,设置为0.5s,代表动画持续0.5s
  animation: moveY var(--duration) cubic-bezier(0.5, -0.5, 1, 1);
}

.plus .iconfont {
  animation: moveX var(--duration) linear;
}

--duration 这种是自定义属性,包括上面的--left --top --x --y这些都是自定义属性,以便重复使用。

三、拓展

  • Houdini API

说到动画,那么非常值得去看看一下Houdini API。是css引擎暴露出来的一组底层api。可以直接和浏览器的css引擎去交互。

  • tween.js,js动画库,创一些平滑的动画效果。不必手动去处理一些复杂的动画细节。

脑子内存记不了太多,勤写一些笔记。

1.gif

☎️ 希望对大家有所帮助,本文有些地方可能考虑不够周到,有些纰漏,就当抛砖引玉,还望您海涵,如有错误,望不吝赐教,欢迎评论区留言互相学习。感谢阅读,祝您开发有乐趣。