购物车添加商品弹跳小球实现

效果图

ball

演示地址

购物车弹跳小球

简述

实现原理是,通过监听+按钮的位置,以及购物车的位置,作为小球的起始和运动的最终位置,但是怎么移动小球呢,我们可以看到,小球的运动轨迹类似抛物线,起始拆分来看就是x,y轴同时进行移动,这里我们可以运用一个技巧,父元素的移动会带动子元素,其中向上抛的部分,可以用贝塞尔曲线实现,下面看看代码。

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
    <script src="https://kit.fontawesome.com/4f492bdd9a.js" crossorigin="anonymous"></script>
    <link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="main">
        <div class="item">
            <img class="cover" src="https://source.unsplash.com/random/800x600?food" alt="">
            <div class="item-right">
                <div>
                    <p>山楂</p>
                </div>
                <div class="item-right-bottom">
                    <div class="price">18</div>
                    <div class="step-counter">
                        <button class="select">选规格</button>
                        <button class="subtract"><i class="fa-solid fa-minus"></i></button>
                        <div class="count">0</div>
                        <button class="plus"><i class="fa-solid fa-plus"></i></button>
                    </div>
                </div>
            </div>
        </div>
        <div class="item">
            <img class="cover" src="https://source.unsplash.com/random/800x600?fruit" alt="">
            <div class="item-right">
                <div>
                    <p>山楂</p>
                </div>
                <div class="item-right-bottom">
                    <div class="price">18</div>
                    <div class="step-counter">
                        <button class="select">选规格</button>
                        <button class="subtract"><i class="fa-solid fa-minus"></i></button>
                        <div class="count">0</div>
                        <button class="plus"><i class="fa-solid fa-plus"></i></button>
                    </div>
                </div>
            </div>
        </div>
        <div class="item">
            <img class="cover" src="https://source.unsplash.com/random/800x600?snack" alt="">
            <div class="item-right">
                <div>
                    <p>山楂</p>
                </div>
                <div class="item-right-bottom">
                    <div class="price">18</div>
                    <div class="step-counter">
                        <button class="select">选规格</button>
                        <button class="subtract"><i class="fa-solid fa-minus"></i></button>
                        <div class="count">0</div>
                        <button class="plus"><i class="fa-solid fa-plus"></i></button>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="buy-car">
        <div class="left">
            <div class="car-pic"><i class="fa-solid fa-shop"></i><span>0</span></div>
            <div class="price">¥18</div>
        </div>
    </div>
</div>

<script>
    let carPic = document.querySelector('.buy-car .left .car-pic')
    let {x, y} = carPic.getBoundingClientRect()
    let plusList = document.querySelectorAll('.plus')
    let subtractList = document.querySelectorAll('.subtract')

    let selectList = document.querySelectorAll('.select')

    let countSum = document.querySelector('.buy-car .left .car-pic span')
    selectList.forEach(select => {
        select.addEventListener('click', function (e) {
            setTimeout(() => {
                select.style.display = 'none';
                let stepCounter = select.parentElement;
                stepCounter.querySelectorAll('.subtract,.plus,.count').forEach(e => {
                    e.style.display = 'block'
                })
            }, 500)
        })
    })

    plusList.forEach(plus => {
        plus.addEventListener('click', function (e) {

            let parentElement = findStpCounter(e.target);


            let ballWarp = document.createElement('div')
            ballWarp.classList.add('ball-warp')
            ballWarp.innerHTML = `<div class="ball"></div>`

            document.body.appendChild(ballWarp)

            let clientRect = e.target.getBoundingClientRect()
            let moveY = y - clientRect.y
            let moveX = x - clientRect.x
            ballWarp.style.setProperty('--left', clientRect.x + 'px');
            ballWarp.style.setProperty('--top', clientRect.y + 'px');
            ballWarp.style.setProperty('--y', moveY + 'px');
            ballWarp.style.setProperty('--x', moveX + 'px');

            ballWarp.addEventListener('animationend', (e) => {
                if (e.animationName === 'moveX') {
                    let countStr = countSum.innerText
                    let countNum = parseInt(countStr) + 1;
                    countSum.innerText = `${countNum}`
                }

                ballWarp.remove()
            })


            let count = parentElement.querySelector('.count')
            let countStr = count.innerText
            let countNum = parseInt(countStr) + 1;
            count.innerText = `${countNum}`
        })
    })

    subtractList.forEach(subtract => {
        subtract.addEventListener('click', function (e) {
            let parentElement = findStpCounter(e.target);
            let count = parentElement.querySelector('.count')
            let countNum = parseInt(count.innerText)
            if (countNum !== 0) {
                countNum--;
                let countSumNum = parseInt(countSum.innerHTML)
                if (countSumNum !==0){
                    countSum.innerText = `${countSumNum - 1}`
                }
            }
            count.innerText = `${countNum}`
        })
    })

    function findStpCounter(e) {
        if (e.classList.contains('step-counter')) {
            return e
        } else {
            return findStpCounter(e.parentElement)
        }
    }
</script>
</body>
</html>

样式部分


* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html, body {
    height: 100%;
}

.container {
    overflow-y: hidden;
    display: flex;
    flex-direction: column;
    max-width: 390px;
    margin: 0 auto;
    border: 1px solid rgba(18, 17, 42, .07);
    border-radius: 5px;
    box-shadow: 0 4px 24px rgba(128, 128, 128, .1);
    height: 100vh;
    position: relative;
}

.container .main{
    flex: 1;
    overflow-y: auto;
    padding-bottom: 60px;
}

.item {
    display: flex;
    gap: 20px;
    padding: 20px 10px;
}

.item .cover {
    width: 80px;
    height: 80px;
    object-fit: cover;
    border-radius: 5px;
    flex-shrink: 0;
}

.item-right {
    display: flex;
    width: 100%;
    flex-direction: column;
    justify-content: space-between;
}

.item-right p {
    font-weight: bold;
}

.item-right-bottom {
    display: flex;
    justify-content: space-between;
}

.item-right-bottom .price {
    color: red;
}

.step-counter {
    position: relative;
    width: 90px;
}

.step-counter button {
    background-color: #2b88f0;
    color: white;
    outline: none;
    border: none;
}

.step-counter .select {
    position: absolute;
    right: 0;
    height: 22px;
    padding: 0 15px;
    border-radius: 999px;
    font-size: 12px;
    line-height: 22px;
}

.step-counter .plus, .subtract {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    cursor: pointer;
    display: none;
}

.step-counter .subtract {
    position: absolute;
    right: 0;
    top: 0;
    background-color: #fff;
    color: #2b88f0;
    border: 1px solid #2b88f0;
    animation: sub-left .8s forwards;
    opacity: 0;
}

.step-counter .plus {
    position: absolute;
    right: 0;
    top: 0;
}

@keyframes sub-left {
    to {
        right: calc(100% - 22px);
        transform: rotate(360deg);
        opacity: 1;
    }
}

.step-counter .count {
    width: 100%;
    display: none;
    text-align: center;
}

.buy-car {
    background-color: #fff;
    z-index: 1000;
    position: absolute;
    left: 0;
    bottom: 0;
    max-width: 390px;
    margin: 0 auto;
    border-top: 1px solid rgba(18, 17, 42, .07);
    height: 60px;
    width: 100%;
    display: flex;
    align-items: center;
    padding: 0 20px;
    justify-content: space-between;
}

.buy-car .left {
    flex: 1;
    display: flex;
    gap: 10px;
}

.buy-car .left .car-pic {
    color: #2b88f0;
    position: relative;
}

.buy-car .left .car-pic span {
    padding: 1px 5px;
    border-radius: 999px;
    background-color: red;
    display: block;
    position: absolute;
    right: -24px;
    top: -12px;
    font-size: 10px;
    text-align: center;
    line-height: 12px;
    color: #fff;
}

.buy-car .left .price {
    color: red;
}

@keyframes moveY {
    to {
        transform: translateY(var(--y));
    }
}

@keyframes moveX {
    to {
        transform: translateX(var(--x));
    }
}

.ball {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background-color: #2b88f0;
    animation: moveY 1s cubic-bezier(0.49, -0.4, 0.75, 0.41);
}

.ball-warp {
    position: fixed;
    top: var(--top);
    left: var(--left);
    animation: moveX 1s;
}

分析

在点击+号的时候,会创建一个.ball-warp的元素,这个元素有个ball的子元素,我们给.ball-warp设置moveX的动画,这个动画就是横向的移动,这会带动子元素ball也横向移动。我们通过css变量的方式设置,移动的位置和初始位置。


购物车添加商品弹跳小球实现
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/buy-car-ball
作者
卑微幻想家
发布于
2023-12-07
许可协议