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 完成计数。

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

相关推荐
L***d6707 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
少云清7 小时前
【功能测试】6_Web端抓包 _Fiddler抓包工具的应用
前端·功能测试·fiddler
豐儀麟阁贵7 小时前
8.5在方法中抛出异常
java·开发语言·前端·算法
zengyuhan5038 小时前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休8 小时前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running8 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔8 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654268 小时前
Android的自定义View
前端
WILLF8 小时前
HTML iframe 标签
前端·javascript
枫,为落叶8 小时前
Axios使用教程(一)
前端