“辉煌の马卡龙”是我最近做的一个纯前端练手项目的名字,核心是做一套马卡龙糖果色风格的静态页面组件。它的实用价值不在于用了多复杂的框架,而在于把“粉嫩但不高饱和”的配色、圆润卡片、高光扫过和轻微悬浮动效,用最朴素的 HTML、CSS 和少量 JavaScript 实现出来。如果你正想给个人博客、产品活动页或展示型页面换一套轻量可爱的视觉,又不想引入重量级组件库,可以直接参考这套做法。下面会拆成几个关键点:色板怎么定、卡片怎么搭、高光和动效怎么做、手机端怎么降级、以及真实部署时会遇到的几类细节问题。
1. 先搞清楚“马卡龙视觉”到底在解决什么问题
很多新手做可爱风页面时,最容易犯的错误是把颜色堆得很满:粉的、蓝的、紫的、黄的都放在一起,结果页面看起来不是“甜”,而是“闹”。马卡龙风格的核心不是单纯用粉色,而是用一组饱和度较低、明度较高、彼此能自然融合的糖果色,构建一种轻松、柔软、不会让人视觉疲劳的界面氛围。
1.1 马卡龙色板怎么提取
我一开始先把项目的主色系统拆成四类,后面所有组件都从这四类里取色,不再临时凭感觉加颜色:
- 背景色:用于页面大背景,通常是非常浅的粉、浅蓝或浅奶油色。
- 主色:用于按钮、标题高亮、激活状态,通常是马卡龙粉、薄荷绿或浅紫。
- 辅助色:用于标签、边框、小图标,可以和主色形成轻对比。
- 文字色:不一定是纯黑,更推荐深棕、灰紫、深藕粉这类柔和色。
实际用的时候,我给四个色系分别定了大致范围,再通过 CSS 变量统一管理。以粉色系为例:
:root { --bg-start: #fff0f5; --bg-end: #e5f4ff; --macaron-pink: #ffb3c6; --macaron-peach: #ffd6a5; --macaron-mint: #b8f2e6; --macaron-blue: #bde0fe; --macaron-lavender: #cdb4db; --card-bg: rgba(255, 255, 255, 0.72); --text-main: #4a3540; --text-sub: #8a7a80; --shadow-card: 0 16px 40px rgba(255, 179, 198, 0.28); --radius-card: 28px; --duration-base: 0.28s; --ease-spring: cubic-bezier(0.22, 1, 0.36, 1); }这里有一个判断标准:如果某个颜色放在页面上,一眼看过去因为太鲜艳而抢了内容本身的注意力,那么这个颜色的饱和度就偏高了。马卡龙风格的通用做法是把饱和度控制在“能看到颜色倾向,但不刺眼”的程度。
1.2 为什么必须用 CSS 变量而不是写死颜色
写死颜色最大的问题不是改起来麻烦,而是改的时候很容易漏掉某处,导致同一类按钮在不同页面出现两三种粉色。用 CSS 变量之后,所有组件都引用同一个变量,想换色只需要改一处:
.btn-macaron { background: var(--macaron-pink); color: #fff; border: 0; }如果后面觉得主色太浅,只需要调整--macaron-pink这一个变量,全站按钮、高亮、进度条都会同步变化。对于个人项目来说,这看起来是“多写了几行”,但对长期维护来说,收益非常大。
2. 页面骨架:先搭出一张能看清“氛围”的启动页
我建议做这类视觉实验时,不要一上来就做完整官网,而是先做一个单屏启动页。原因很简单:启动页结构固定,包含背景、标题、按钮、卡片,足够验证配色和动效,又不会因为内容太多而分不清效果是不是真的好看。
2.1 最小 HTML 结构
这一步的目标不是做完整模板,而是建立三个基础区块:顶部导航、首屏标题区、卡片展示区。下面是一个可以直接保存运行的示例。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>辉煌の马卡龙</title> <link rel="stylesheet" href="style.css" /> </head> <body> <header class="page-header"> <span class="logo">辉煌の马卡龙</span> <nav class="menu"> <a href="#cards">卡片</a> <a href="#colors">配色</a> <a href="#motion">动效</a> </nav> </header> <section class="hero"> <h1>甜味视觉,代码实现</h1> <p>马卡龙色系 + 轻量动效,适合博客首页、个人展示页和活动落地页。</p> <a class="btn-macaron" href="#cards">开始浏览</a> </section> <main class="card-grid" id="cards"> <article class="card"> <h2>草莓</h2> <p>粉底白字,适合作为首屏主卡片。</p> </article> <article class="card"> <h2>薄荷</h2> <p>清爽冷调,用于补充视觉层次。</p> </article> <article class="card"> <h2>香芋</h2> <p>柔和紫色,表达轻快和一点点个性。</p> </article> </main> </body> </html>这个结构里,卡片内容先不要填太多,用三张带短文案的卡片就够验证样式。后面真正接项目时,再把卡片内容替换成博客文章摘要、产品入口或图片链接。
2.2 背景和视觉层次怎么安排
背景不要用纯色。纯色会让大面积的浅粉显得“平”,建议用两个浅色做渐变,再配合一个或多个径向光斑,模拟柔和打光。常见的做法是:
body { margin: 0; min-height: 100vh; background: linear-gradient(135deg, var(--bg-start), var(--bg-end)); font-family: "PingFang SC", "Microsoft YaHei", sans-serif; color: var(--text-main); } .hero { min-height: 60vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px 20px; background: radial-gradient(circle at 20% 20%, rgba(255, 179, 198, 0.5), transparent 40%), radial-gradient(circle at 80% 80%, rgba(189, 224, 254, 0.5), transparent 45%); }radial-gradient 的光斑不需要做太多,两三个就够。它解决的是“背景显得干”的问题,让视觉重心自然落在中间的内容上。
3. 卡片组件与核心动效:把“外壳”和“光泽”做出来
马卡龙页面里,卡片是最常用的承载单元。它能放文章、放产品、放链接,也可以通过不同颜色区分用途。卡片的核心不是边框和阴影,而是圆角、留白、阴影和高光这四个变量之间的配合。
3.1 卡片基础样式
我把卡片的基础样式做成一个通用的.card类,内部元素可以根据场景继续扩展。
.card { border-radius: var(--radius-card); background: var(--card-bg); box-shadow: var(--shadow-card); padding: 28px; position: relative; overflow: hidden; transition: transform var(--duration-base) var(--ease-spring), box-shadow var(--duration-base) var(--ease-spring); backdrop-filter: blur(8px); } .card h2 { margin: 0 0 8px; font-size: 20px; } .card p { margin: 0; color: var(--text-sub); line-height: 1.7; }这里有几个容易被忽视的细节:
overflow: hidden是为了让高光效果不会溢出圆角边界,但如果卡片内部有下拉菜单或 Tooltip,要注意别被裁掉。backdrop-filter: blur(8px)能做出磨砂玻璃感,但它并不是所有浏览器都支持,而且大面积使用时对低端设备性能有压力。transition只对transform和box-shadow做过渡,不要把所有属性都放进 transition 里,否则每次元素位置变化都可能引发额外渲染。
3.2 高光扫过效果怎么做
“辉煌”在这个项目里的体现,我选择了类似高光扫过的效果:当鼠标进入卡片时,一道柔和的白色光带从左侧滑过。这个效果不需要图片资源,只需要在卡片上面叠加一个伪元素,再用 transform 控制位置。
.card::before { content: ""; position: absolute; top: -60%; left: -60%; width: 80%; height: 200%; background: linear-gradient( 115deg, transparent 0%, rgba(255, 255, 255, 0.55) 45%, transparent 70% ); transform: translateX(-120%) rotate(8deg); pointer-events: none; transition: transform 0.7s ease; } .card:hover::before { transform: translateX(150%) rotate(8deg); }这段代码的关键点是伪元素必须比较大,才能保证扫过时覆盖整个卡片表面。pointer-events: none一定不能漏,否则光带会挡住鼠标事件,导致卡片内部的链接点击困难。
3.3 悬浮抬起和阴影变化
卡片悬浮时,最常见的做法是向上位移几像素,同时让阴影范围变大、颜色变深。位移用 transform,不要直接改 top 或 margin:
.card:hover { transform: translateY(-6px); box-shadow: 0 24px 50px rgba(255, 179, 198, 0.38); }原因是 transform 只触发合成器,性能更好,不会影响其他元素的布局。如果改成margin-top: -6px,整个页面元素都会跟着重新排布,造成不必要的回流。实测时,如果页面在悬浮后出现“抖动”或“卡顿”,优先检查是不是改动了布局属性。
3.4 动效时长怎么定
动效时长是一个经常被低估的参数。太短会显得生硬,太长会拖慢操作反馈。我在这套组件里把过渡时间拆成两个档位:
| 动效类型 | 推荐时长 | 缓动函数 | 原因 |
|---|---|---|---|
| 卡片悬浮 | 0.24s - 0.32s | cubic-bezier(0.22, 1, 0.36, 1) | 反馈及时,又不生硬 |
| 高光扫过 | 0.6s - 0.8s | ease | 强调视觉流动,太短看不清 |
| 页面进入 | 0.4s - 0.6s | ease-out | 需要给用户足够的时间感知 |
| 弹窗出现 | 0.2s - 0.3s | ease-out | 弹窗需要快速响应,不能拖沓 |
对于只做学习项目的情况,默认 0.28s 基本够用。如果是真实产品里的核心操作按钮,我建议缩短到 0.18s 到 0.22s,减少操作等待感。
4. 用最小 JS 完成交互:页面进入、悬浮增强和用户偏好
纯 CSS 能完成大部分视觉动效,但有几个场景需要 JS 配合:进入视口时再播放动画、记住用户是否关闭动效、以及区分鼠标设备和触屏设备。
4.1 页面进入滚动动画
不需要引入动画库,直接用 IntersectionObserver 观察卡片进入视口的时机,然后添加一个类名触发 CSS 动画。
.card { opacity: 0; transform: translateY(24px); transition: opacity 0.5s ease-out, transform 0.5s ease-out; } .card.is-visible { opacity: 1; transform: translateY(0); }const cards = document.querySelectorAll('.card'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry, index) => { if (entry.isIntersecting) { entry.target.style.transitionDelay = `${index * 80}ms`; entry.target.classList.add('is-visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.2 }); cards.forEach((card) => observer.observe(card));加了transitionDelay之后,多张卡片会依次出现,形成一个小瀑布效果。但注意延迟不要设置得太长,否则用户已经滚动到目标位置,动画还没播完,体验会很拖沓。三张卡片以内,每张延迟 80ms 到 120ms 是比较稳的范围。
4.2 鼠标悬浮和触屏处理
卡片悬浮高光在鼠标上看起来不错,但在手机上根本没有 hover 状态。如果只是做展示,可以接受不生效;但更好的做法是用 JS 判断当前设备是否支持 hover,避免在手机上误触发高光导致卡片内容跳动。
const canHover = window.matchMedia('(hover: hover)').matches; if (canHover) { document.documentElement.classList.add('has-hover'); }然后在 CSS 里把高光和悬浮效果放到.has-hover作用域下:
.has-hover .card:hover { transform: translateY(-6px); box-shadow: 0 24px 50px rgba(255, 179, 198, 0.38); } .has-hover .card:hover::before { transform: translateX(150%) rotate(8deg); }这样在触屏设备上,卡片仍然正常显示,只是不会出现无意义的悬浮变化。
4.3 降低动态效果的偏好开关
很多操作系统自带“减弱动态效果”设置,页面应该尊重这个偏好。一个简单的思路是:检测到用户偏好后,立即把动画和过渡时间降到几乎为零。
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReduced) { document.documentElement.classList.add('reduce-motion'); }.reduce-motion *, .reduce-motion *::before, .reduce-motion *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }这个处理方式成本很低,但对部分用户价值很高。页面如果长时间高频闪烁,可能引起不适。做前端项目时有这个开关,也说明不是只追求炫效果,而是考虑了真实用户的体验边界。
5. 响应式和浏览器兼容:手机上也别翻车
做这类视觉页面时,很容易在电脑浏览器里看得很满意,拿到手机上却发现光斑位置不对、卡片过宽、或者滚动卡顿。所以从项目一开始,就要把响应式和兼容性放进验收标准。
5.1 响应式断点
马卡龙风格常用的是卡片网格。最小屏默认单列,平板宽度变成两列,桌面宽度变成三列。
.card-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 20px; padding: 24px; max-width: 1200px; margin: 0 auto; } @media (min-width: 640px) { .card-grid { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 1024px) { .card-grid { grid-template-columns: repeat(3, 1fr); } }这里有一个判断标准:不要只按设备宽度来决定列数,还要看卡片实际内容长度。如果卡片内部是长标题,三列时可能换行太严重,这时可以结合minmax或者把标题字号降一档。
5.2 兼容性检查优先级
这套组件里我用到的 CSS 特性并不多,但每个特性的兼容性不一样。建议在部署前按下面的优先级检查:
| CSS 特性 | 兼容性提醒 | 降级方案 |
|---|---|---|
| CSS 变量 | 现代浏览器都支持 | 不兼容时使用 fallback 值 |
| backdrop-filter | 部分浏览器不支持 | 直接显示半透明背景色,不为空 |
| transform 动画 | 基本都支持 | 不推荐降级,但别只依赖这一种反馈 |
| radial-gradient | 很老了,基本没问题 | 未设置渐变时显示纯色背景 |
| IntersectionObserver | 现代浏览器支持 | 回退到直接显示所有卡片 |
建议的检查方式是:在 Chrome、Edge、Safari 和技术相对旧一些的 Chrome for Android 里各看一遍。如果某个效果是装饰性的,不承载关键内容,那做降级处理就好,不需要为了它强行兼容。
5.3 低端手机上的性能处理
低端机的卡顿来源通常不是 CSS 变量,而是两个容易被忽略的地方:大面积 backdrop-filter 和连续执行的 JavaScript 定时器。
我在测试时发现,如果页面里同时挂了 8 到 10 张带磨砂背景的卡片,低端安卓机在滚动时会有明显掉帧。解决方法不是把机器换掉,而是把模糊效果从背景上移除,改成半透明纯色,或者只在首屏主卡片上启用。
.card { background: rgba(255, 255, 255, 0.92); } @supports (backdrop-filter: blur(8px)) { .card { background: rgba(255, 255, 255, 0.72); backdrop-filter: blur(8px); } }这样在不支持 backdrop-filter 的浏览器里,卡片仍然是白色半透明底,不会变成全透明导致文字看不清。
6. 从单页到组件化:后续怎么扩展成真实可用项目
启动页跑通之后,不要急着把所有想做的效果都堆进同一个页面。我更建议把它拆成可复用的小块:色板、基础卡片、按钮、动效开关、交互逻辑。这样不管是继续用静态页面,还是迁移到 Vue、React,都比较轻松。
6.1 用>html[data-theme="mint"] { --bg-start: #e6fbf4; --bg-end: #eef6ff; --macaron-pink: #a8e6cf; --macaron-peach: #ffd6a5; --macaron-lavender: #b8dfe0; --shadow-card: 0 16px 40px rgba(168, 230, 207, 0.3); } html[data-theme="grape"] { --bg-start: #f6efff; --bg-end: #fff0f8; --macaron-pink: #cdb4db; --macaron-peach: #ffc6d7; --macaron-lavender: #b39bc8; --shadow-card: 0 16px 40px rgba(205, 180, 219, 0.3); }
JS 里只需要一行切换:
document.documentElement.dataset.theme = 'mint';这里有个容易踩的坑:如果你给每个主题都重新定义很多变量,CSS 会变得很长。建议只在主题里覆盖有意义的变量,例如背景、主色、卡片阴影,文字色和圆角这类全局变量保持统一。
6.2 迁移到 Vue 或 React 时的思路
如果以后要把这套样式接入组件库,建议不要直接把 CSS 类搬进去,而是把颜色和时长提取成“设计令牌”。在 React 里可以用 CSS 变量继续跨组件共享,在 Vue 里也一样,不需要额外引入主题库。
真正要迁移的其实是结构:
- 把
.card拆成Card.vue或Card.tsx组件。 - 把
--macaron-pink这类变量放到全局样式里。 - 把 IntersectionObserver 逻辑封装成
useInView之类的 Hook。 - 把高光伪元素保留在组件内部,避免影响外部布局。
这一步不会让页面更快,但会让后续迭代更稳。
6.3 如何避免页面“看起来像 Demo”
很多马卡龙风格的页面第一眼好看,看久了却觉得缺少内容感,原因通常是卡片里只有颜色没有信息层级。要让页面更像正式产品,至少需要补三个东西:
- 有明确主次的内容标题,而不是随便填充的“卡片一”“卡片二”。
- 统一的间距系统,卡片内外间距、标题与正文间距、区块间距保持比例。
- 经过挑选的字体和字号,正文不要使用太多字重和大小变化。
视觉风格只是外衣,内容层级才是页面经得起看的原因。这个结论我在自己很多练习项目里都有体会。
7. 常见问题排查:先看现象,再改参数
把页面做完后,大概率会遇到几个典型问题。我整理了一份自己排查这类页面时的固定顺序,能少走很多弯路。
7.1 动效卡顿
如果卡片悬浮、高光扫过时画面不流畅,一般不是电脑性能问题,而是没有按下面顺序检查:
- 先确认是不是大面积 backdrop-filter 导致的,可以临时去掉观察。
- 再确认 transform 和 opacity 是否正常使用,避免在动画里改变 width、height、top、left。
- 检查卡片数量,如果一次性创建了太多复杂阴影或渐变元素,低端设备会压力很大。
- 最后打开开发者工具的 Performance 面板,查看有没有持续运行的脚本。
如果页面里只有三五张卡片还卡,那大概率不是数量问题,而是触发了连续布局。
7.2 高光完全不显示
高光消失通常不是动画问题,而是这几个原因:
- 卡片设置了 overflow 为 visible,伪元素溢出到可见区域外。
- 伪元素宽高不足,无法覆盖卡片表面。
- 卡片内部子元素自带背景色,把伪元素遮挡住了。
排查时先给伪元素加一个临时高亮背景,比如红色,看它有没有出现在卡片上。出现后再检查 transform 和层级关系。
7.3 颜色和设计稿差别很大
同一套马卡龙配色,在不同显示器或不同浏览器里看起来可能不同。这不是代码错误,而是色域和屏幕校准的差异。处理方式是:
- 不要在系统夜间模式下直接调色,容易把颜色调得过亮或过暗。
- 把主要颜色集中在 CSS 变量里,后续微调只需要改一处。
- 尽量使用 sRGB 色值,避免使用过广色域的下划线描述。
如果追求更精确的跨屏一致性,可以进一步了解颜色配置文件,但对个人项目来说,把主色集中管理已经足够。
7.4 JS 引入后页面白屏
如果页面在加入脚本后突然白屏,优先检查脚本是否在 DOM 加载之前执行了,导致选择器找不到元素。最简单的处理是把 script 放在 body 底部,或者用 DOMContentLoaded 包裹。
document.addEventListener('DOMContentLoaded', () => { // 初始化逻辑 });这个问题和 CSS 无关,但很多新手会误以为是布局被改坏了,所以在排查时要先看控制台报错,再动页面结构。
最后的落地建议
“辉煌の马卡龙”这套小项目,真正适合的路线是先做单页、再拆组件、最后接真实内容。不推荐一上来就追求所有动效都上,也不推荐把所有颜色都铺满。页面好看与否,往往取决于色板是否克制、动效是否有节奏、内容是否有主次。
我从这套练习里得到的一个很实际的经验是:视觉风格的代码并不难,难的是在面对“这里要不要加个动画”“这种粉色要不要再亮一点”这些选择时,能克制住不断加东西的冲动。把颜色变量、动效时长、降级开关和响应式结构先固定下来,后面再往里填充真实内容,页面反而更容易稳定和耐看。