HTML&CSS&JS:卡片3D倾斜效果

这个 HTML 文件实现了一个 3D 效果的图片卡片交互效果,当鼠标悬停在卡片上时,卡片会根据鼠标位置产生 3D 倾斜效果,并显示一个高光反射效果。


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

演示效果

HTML&CSS

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>卡片3D倾斜效果</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        @property --x {
            syntax: '<number>';
            initial-value: 0;
            inherits: true;
        }

        @property --y {
            syntax: '<number>';
            initial-value: 0;
            inherits: true;
        }

        @layer reset {
            * {
                margin: 0;
            }

            svg,
            img {
                display: block;
            }
        }

        .visually-hidden {
            position: fixed;
            top: -999vh;
            left: -999vw;
            pointer-events: none;
        }

        main {
            display: grid;
            grid: auto-flow / repeat(auto-fit, 300px);
            gap: 30px;
            place-items: center;
            place-content: center;
            height: 100vh;
            background:radial-gradient(#424242 0 1px, #0000 1px 10px) 0 / 1em 1em,linear-gradient(#EDEEEE, #E0E0E0)
        }

        .card {
            width: 300px;
            aspect-ratio: 9/12;
            perspective: 1200px;
            transform-style: preserve-3d;
        }

        figure {
            --atan: calc(-1 * atan2(var(--x), var(--y)));
            position: relative;
            height: 100%;
            border-radius: 1em;
            transform: rotateX(calc(var(--x) * 1deg)) rotateY(calc(var(--y) * 1deg));
            overflow: hidden;
            will-change: transform;
            transition: --x 400ms ease,--y 400ms ease;
        }

        figure:hover {
            transition-duration: 0s;
        }

        figure:hover::after {
            opacity: 1;
        }

        figure::after {
            content: '';
            position: absolute;
            inset: 0;
            background: url('https://images.unsplash.com/photo-1615715874901-ad6a07ec5c88?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3NTQ0MjE2ODd8&ixlib=rb-4.1.0&q=85') center / cover;
            mask-image: conic-gradient(from var(--atan), #0000, #fffa, #0000, #fffa, #0000);
            backdrop-filter: url(#filterEdges);
            opacity: 0;
            mix-blend-mode: plus-lighter;
            transition: opacity 200ms;
        }

        figure img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    </style>
</head>

<body>
    <svg width="0" height="0">
        <defs>
            <filter id="filterEdges">
                <feConvolveMatrix kernelMatrix="0 1 0 1 -4 1 0 1 0" order="3 3" bias="0" divisor="1"
                    preserveAlpha="true" />
            </filter>
        </defs>
    </svg>

    <main>
        <div class="card">
            <figure>
                <img src="https://images.unsplash.com/photo-1579783928621-7a13d66a62d1?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3NTQ0MDUyNDN8&ixlib=rb-4.1.0&q=85"
                    alt="">
            </figure>
        </div>
        <div class="card">
            <figure>
                <img src="https://images.unsplash.com/photo-1575396574188-ccf23d32d4b8?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3NTQ0ODM3MTN8&ixlib=rb-4.1.0&q=85"
                    alt="">
            </figure>
        </div>
    </main>
    <script>
        const cards = document.querySelectorAll('.card');
        let mouseEnterTarget = null;
        let targetBounds = null;
        function handleMouseEnter(e) {
            mouseEnterTarget = e.target;
            targetBounds = e.target.getBoundingClientRect();
            mouseEnterTarget.addEventListener('mousemove', handleMouseMove);
            mouseEnterTarget.addEventListener('mouseleave', handleMouseLeave);
        }
        function handleMouseLeave(e) {
            mouseEnterTarget.style.setProperty('--x', 0);
            mouseEnterTarget.style.setProperty('--y', 0);
            mouseEnterTarget.removeEventListener('mousemove', handleMouseMove);
        }
        function handleMouseMove(e) {
            const { offsetX, offsetY } = e;
            const centerX = offsetX - (targetBounds.width * 0.5);
            const centerY = offsetY - (targetBounds.height * 0.5);
            const posX = Math.round(-1 * centerX * 0.1);
            const posY = Math.round(centerY * 0.1);
            mouseEnterTarget.style.setProperty('--x', posY);
            mouseEnterTarget.style.setProperty('--y', posX);
        }
        cards.forEach(card => {
            card.addEventListener('mouseenter', handleMouseEnter);
        })
    </script>
</body>

</html>

HTML

  • svg:定义了一个滤镜效果,用于创建边缘高光效果
  • main:作为主容器,包含两个.card 元素
  • 每个.card 包含一个 figure 和 img,显示来自 Unsplash 的图片

CSS

CSS 变量和属性定义

  • 使用@property 定义了--x 和--y 两个自定义属性,用于控制 3D 旋转
  • 这些属性有初始值 0,并支持动画过渡效果

布局样式

  • 主容器使用 CSS Grid 布局,自动适应不同屏幕尺寸
  • 卡片固定宽度 300px,保持 9:12 的宽高比

3D 效果

  • .card 设置了 perspective: 1200px 和 transform-style: preserve-3d 创建 3D 空间
  • figure 元素使用 rotateX 和 rotateY 根据--x 和--y 变量进行 3D 旋转
  • 使用 will-change: transform 优化动画性能

高光效果

  • 通过::after 伪元素创建高光效果
  • 使用 conic-gradient 创建基于角度的遮罩
  • backdrop-filter 应用 SVG 定义的边缘滤镜效果
  • mix-blend-mode: plus-lighter 增强高光效果

JavaScript

事件监听

  • 为每个卡片添加 mouseenter 事件监听
  • 进入时添加 mousemove 和 mouseleave 监听

鼠标移动处理

  • 计算鼠标相对于卡片中心的位置
  • 将位置转换为旋转角度(--x 和--y 值)
  • 这些值会通过 CSS 变量传递给 CSS,实现 3D 旋转效果

鼠标离开处理

  • 重置旋转角度为 0
  • 移除 mousemove 事件监听

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

相关推荐
byzh_rc21 小时前
[微机原理与系统设计-从入门到入土] 微型计算机基础
开发语言·javascript·ecmascript
m0_4711996321 小时前
【小程序】订单数据缓存 以及针对海量库存数据的 懒加载+数据分片 的具体实现方式
前端·vue.js·小程序
编程大师哥21 小时前
Java web
java·开发语言·前端
A小码哥21 小时前
Vibe Coding 提示词优化的四个实战策略
前端
Murrays21 小时前
【React】01 初识 React
前端·javascript·react.js
大喜xi21 小时前
ReactNative 使用百分比宽度时,aspectRatio 在某些情况下无法正确推断出高度,导致图片高度为 0,从而无法显示
前端
helloCat21 小时前
你的前端代码应该怎么写
前端·javascript·架构
电商API_1800790524721 小时前
大麦网API实战指南:关键字搜索与详情数据获取全解析
java·大数据·前端·人工智能·spring·网络爬虫
康一夏21 小时前
CSS盒模型(Box Model) 原理
前端·css
web前端12321 小时前
React Hooks 介绍与实践要点
前端·react.js