HTML&CSS:超丝滑抛物线飞入购物车效果

这个 HTML 文件实现了一个极简的"咖啡飞入购物车"交互动画页面,点击任意一杯咖啡,咖啡图标会沿抛物线轨迹飞入底部购物车,同时购物车数字实时累加,全程无框架、无图片精灵、仅用 1 行 JS 计数。


大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信回复源码两字,我会发送完整的压缩包给你。

演示效果

HTML&CSS

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .items {
            --y: 100px;

            &:not(.cart) {
                transform: translate(var(--x), calc(-1 * var(--y)));
            }

            &.espresso {
                --x: 0px;
            }

            &.cappuccino {
                --x: -100%;
            }

            &.flat-white {
                --x: 100%;
            }

            input:checked {
                transform: translate(calc(-1 * var(--x)), var(--y)) scale(0);
                transition: transform .6s linear;
            }
        }

        @layer layout {
            main {
                width: 450px;
                aspect-ratio: 1.5;
                contain: layout;

                .items {
                    width: 140px;
                    aspect-ratio: 1;
                    position: absolute;
                    inset: auto 0 0;
                    margin-inline: auto;

                    &.cart {
                        width: 80px;
                        border-radius: 50%;
                        contain: layout;

                        output {
                            display: block;
                            width: 30px;
                            aspect-ratio: 1;
                            position: absolute;
                            offset: content-box 6% 0deg / -10px 0;
                            border-radius: inherit;
                        }
                    }

                    input {
                        position: absolute;
                        inset: 0;
                    }
                }

                &:has(input:focus-visible) [hidden] {
                    display: initial;
                }

                &:not(:has(input:checked)) .cart::after {
                    display: none;
                }
            }
        }

        @layer theme {
            .cart {
                font-size: 32px;
                text-align: center;
                align-content: center;
                color: transparent;
                text-shadow: 0 0 0 lightsteelblue;
                background-color: rgb(250, 250, 251);
                box-shadow: 2px 2px 6px rgb(230, 230, 231), -2px -2px 6px rgb(250, 250, 251), inset 2px 2px 3px #fff;

                output {
                    font-size: 12px;
                    align-content: inherit;
                    color: white;
                    background-color: rgba(0, 0, 70, 0.2);
                }
            }

            input {
                appearance: none;
                background: center/contain;
                cursor: pointer;

                .espresso & {
                    background-image: url("https://www.coca-cola.com/content/dam/onexp/in/en/home-page-test-img/brands/costa/costa-resized/cappuccino.png");
                }

                .cappuccino & {
                    background-image: url("https://www.coca-cola.com/content/dam/onexp/in/en/home-page-test-img/brands/costa/costa-resized/espresso.png");
                }

                .flat-white & {
                    background-image: url("https://www.coca-cola.com/content/dam/onexp/in/en/home-page-test-img/brands/costa/costa-resized/flat%20white.png");
                }

                &:focus-visible {
                    outline: none;

                    .items:has(&) {
                        border: 2px solid;
                        border-radius: 4px;
                    }
                }
            }
        }

        body {
            height: 100vh;
            margin: 0;
            display: grid;
            place-content: center;
            user-select: none;
            -webkit-user-select: none;
            cursor: default;
            font: 14px 'poppins';
        }
    </style>
</head>

<body>
    <main>
        <div class="items cart" data-count="0" aria-live="assertive">
            &#x1F6D2;<output data-count="0" aria-label="Number of items in cart is">0</output>
        </div>
        <div class="items cappuccino">
            <input type="radio" name="r1" title="Cappuccino">
            <input type="radio" name="r1" title="Cappuccino">
        </div>
        <div class="items espresso">
            <input type="radio" name="r2" title="Espresso">
            <input type="radio" name="r2" title="Espresso">
        </div>
        <div class="items flat-white">
            <input type="radio" name="r3" title="Flat White">
            <input type="radio" name="r3" title="Flat White">
        </div>
    </main>
    <script>
        let n = 0;
        const CART_CNT = document.querySelector("output");
        document.querySelectorAll("[type='radio']").forEach(radio => {
            radio.onclick = () => {
                CART_CNT.innerText = ++n;
                CART_CNT.setAttribute("arial-label", `${n}`)
            }
        });
    </script>
</body>

</html>

HTML

  • main:固定 450×300 盒子,居中展示。
  • .cart:圆形气泡购物车,内部 output 实时显示数量。
  • .items.*:每组咖啡放 两个同名 radio 保证点击后仍能继续点击同一组。
  • aria-live:屏幕阅读器实时播报购物车数量变化。

CSS

变量驱动的抛物线动画

css 复制代码
.items{
  --y:100px;                /* 垂直位移 */
  --x:0/100%/-100%;         /* 水平位移 */
  transform:translate(var(--x),calc(-1*var(--y))); /* 起点 */
}
input:checked{
  transform:translate(calc(-1*var(--x)),var(--y)) scale(0);
  transition:transform .6s linear;   /* 抛物线 + 缩小消失 */
}
  • 自定义属性 --x / --y 把三组咖啡的初始位置一次性定义完。
  • 选中后反向位移 → 形成「飞进购物车」的错觉。

气泡购物车

css 复制代码
.cart{
  border-radius:50%;
  background:rgb(250,250,251);
  box-shadow:
    2px 2px 6px rgb(230,230,231),
   -2px -2px 6px rgb(250,250,251),
   inset 2px 2px 3px #fff;
}
  • 三层阴影:外凸 + 内凹 → 软软的气泡感。
  • text-shadow:0 0 0 lightsteelblue 让 🛒 图标呈现单色。

响应式 & 可访问性

css 复制代码
@layer layout{ /* 结构 */ }
@layer theme { /* 皮肤 */ }

@media(max-width:768px){ ... }
@media(max-width:414px){ ... }
  • CSS 原生分层(@layer)实现「皮肤」与「布局」解耦。
  • 小屏去掉左右「耳朵圆角」,避免被裁切。

JavaScript

JavaScript 复制代码
let n = 0;
const CART_CNT = document.querySelector('output');
document.querySelectorAll('[type="radio"]').forEach(radio => {
  radio.onclick = () => {
    CART_CNT.innerText = ++n;
    CART_CNT.setAttribute('aria-label', `${n}`);
  };
});
  • ++n:利用「同名 radio 仍可重复点击」的特性,持续累加。
  • aria-label:同步给读屏器,确保无障碍。
  • 一行核心 JS 完成计数。

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
昔人'1 分钟前
css `lh`单位
前端·css
前端君35 分钟前
实现最大异步并发执行队列
javascript
Nan_Shu_6142 小时前
Web前端面试题(2)
前端
知识分享小能手2 小时前
React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
前端·javascript·vue.js·学习·react.js·react·anti-design-vue
2501_918126912 小时前
用html5写一个flappybird游戏
css·游戏·html5
蚂蚁RichLab前端团队3 小时前
🚀🚀🚀 RichLab - 花呗前端团队招贤纳士 - 【转岗/内推/社招】
前端·javascript·人工智能
孩子 你要相信光3 小时前
css之一个元素可以同时应用多个动画效果
前端·css
萌萌哒草头将军3 小时前
Oxc 和 Rolldown Q4 更新计划速览!🚀🚀🚀
javascript·vue.js·vite
huangql5203 小时前
npm 发布流程——从创建组件到发布到 npm 仓库
前端·npm·node.js
Qlittleboy3 小时前
uniapp如何使用本身的字体图标
javascript·vue.js·uni-app