HTML设计
约 2400 字大约 8 分钟
htmlcss特效
2020-05-06
一句话摘要:通过三个实战案例,掌握毛玻璃效果、Liquid Glass 和新拟物化设计的 CSS 实现原理。
背景与动机
现代 Web 设计追求沉浸感和层次感,毛玻璃效果和新拟物化设计是两种热门的视觉风格。这篇文章通过三个可交互的 demo,帮助你理解这些效果背后的 CSS 技术原理。
适合有一定 CSS 基础的前端开发者,你将学到:
backdrop-filter的使用方法- 新拟物化的阴影技巧
- JavaScript 模拟
background-attachment: fixed的原理
案例 1:经典毛玻璃效果
这是最常见的毛玻璃实现方式,通过 backdrop-filter: blur() 对背景进行模糊处理。
核心原理:
- 背景模糊:使用
backdrop-filter对元素背后的内容进行模糊 - 深度层次:通过多层阴影模拟玻璃的光影效果
- 拖拽交互:JavaScript 实现元素拖动,验证毛玻璃效果随位置变化
HTML
<div class="box">
<div class="glass">
<div class="light" data-js-background-attachment-fixed></div>
<div class="drag-me">Drag Me</div>
</div>
</div>JavaScript
/**
* 模拟 background-attachment: fixed 效果
* 通过动态计算元素位置来更新背景位置
*/
const updateDataJSBackgroundAttachmentFixedElements = () => {
const elements = document.querySelectorAll(
"[data-js-background-attachment-fixed]",
);
for (const element of elements) {
if (!(element instanceof HTMLElement)) continue;
const clientRect = element.getBoundingClientRect();
const backgroundPositionX = `${(-clientRect.x).toString()}px`;
const backgroundPositionY = `${(-clientRect.y).toString()}px`;
element.style.backgroundPositionX = backgroundPositionX;
element.style.backgroundPositionY = backgroundPositionY;
}
};
const initDataJSBackgroundAttachmentFixed = () => {
requestAnimationFrame(() => {
updateDataJSBackgroundAttachmentFixedElements();
initDataJSBackgroundAttachmentFixed();
});
};
// 拖拽功能实现
const glass = document.querySelector('.glass');
const dragMe = document.querySelector('.drag-me');
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
dragMe.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === dragMe) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, glass);
}
}
function dragEnd() {
isDragging = false;
}
function setTranslate(xPos, yPos, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}
initDataJSBackgroundAttachmentFixed();CSS
.box {
height: 360px;
margin: 0;
padding: 0;
background-image: url('https://w.wallhaven.cc/full/45/wallhaven-45dd35.jpg');
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
.glass {
height: 300px;
width: 200px;
margin: 0;
padding: 0;
/* 毛玻璃核心属性 */
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
/* 多层阴影模拟玻璃质感 */
box-shadow:
inset -0.75px -0.5px rgba(255, 255, 255, 0.1),
inset +0.75px +0.5px rgba(255, 255, 255, 0.025),
3px 2px 10px rgba(0, 0, 0, 0.25),
inset 0px 0px 10px 5px rgba(255, 255, 255, 0.025),
inset 0px 0px 40px 5px rgba(255, 255, 255, 0.025);
position: relative;
border-radius: 5px;
overflow: hidden;
}
.glass::before {
content: "";
background-repeat: repeat;
background-size: 750px;
opacity: 0.075;
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: -1;
background-attachment: fixed;
}
.drag-me {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
background-color: rgba(12, 13, 14, 0.75);
color: rgba(201, 216, 232, 0.75);
cursor: move;
}案例 2:Liquid Glass 液态玻璃效果
Liquid Glass 是一种更加现代的玻璃风格,特点是更柔和的透明度和更大的模糊半径。
与经典毛玻璃的区别:
- 更高的背景透明度(
rgba(255, 255, 255, 0.15)) - 更强的模糊效果(
blur(12px)) - 增加饱和度参数(
saturate(150%)) - 更柔和的边框和阴影
HTML
<div class="box">
<div class="glass">
<div class="light" data-js-background-attachment-fixed></div>
<div class="drag-me">Drag Me</div>
</div>
</div>JavaScript
const updateDataJSBackgroundAttachmentFixedElements = () => {
const elements = document.querySelectorAll(
"[data-js-background-attachment-fixed]",
);
for (const element of elements) {
if (!(element instanceof HTMLElement)) continue;
const clientRect = element.getBoundingClientRect();
const backgroundPositionX = `${(-clientRect.x).toString()}px`;
const backgroundPositionY = `${(-clientRect.y).toString()}px`;
element.style.backgroundPositionX = backgroundPositionX;
element.style.backgroundPositionY = backgroundPositionY;
}
};
const initDataJSBackgroundAttachmentFixed = () => {
requestAnimationFrame(() => {
updateDataJSBackgroundAttachmentFixedElements();
initDataJSBackgroundAttachmentFixed();
});
};
const glass = document.querySelector('.glass');
const dragMe = document.querySelector('.drag-me');
let isDragging = false;
let currentX, currentY, initialX, initialY;
let xOffset = 0, yOffset = 0;
dragMe.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === dragMe) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, glass);
}
}
function dragEnd() {
isDragging = false;
}
function setTranslate(xPos, yPos, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}
initDataJSBackgroundAttachmentFixed();CSS
.box {
height: 360px;
margin: 0;
padding: 0;
background-image: url('https://w.wallhaven.cc/full/45/wallhaven-45dd35.jpg');
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
.glass {
height: 200px;
width: 150px;
margin: 0;
padding: 0;
border-radius: 15px;
position: relative;
/* Liquid Glass 核心样式 */
background-color: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(12px) saturate(150%);
-webkit-backdrop-filter: blur(12px) saturate(150%);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease;
overflow: hidden;
padding: 30px;
text-align: center;
color: #333;
}
.glass:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
}
.drag-me {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
font-size: 1.8em;
margin-bottom: 15px;
color: #2a7aff;
cursor: move;
}案例 3:新拟物化设计(Neumorphism)
新拟物化是另一种流行的设计风格,通过精心控制的阴影模拟真实的凸起和凹陷效果。
核心原理:
- 双阴影系统:一个深色阴影 + 一个浅色阴影,模拟光源照射
- 凸起效果:外阴影(
box-shadow) - 凹陷效果:内阴影(
box-shadow: inset) - 同色背景:元素背景色与容器背景色相同,仅靠阴影区分层次
HTML
<div class="box">
<div class="container">
<header class="neu-header">
<i class="fas fa-bars"></i>
<h1>新拟物化设计</h1>
<i class="fas fa-cog"></i>
</header>
<main class="content">
<div class="neu-card neu-convex">
<i class="fas fa-music"></i>
<h2>音乐播放</h2>
<p>享受您的音乐,体验极致音质。</p>
<div class="neu-buttons">
<button class="neu-button neu-concave"><i class="fas fa-backward"></i></button>
<button class="neu-button neu-convex"><i class="fas fa-play"></i></button>
<button class="neu-button neu-concave"><i class="fas fa-forward"></i></button>
</div>
</div>
<div class="neu-section">
<div class="neu-input-wrapper neu-concave">
<input type="text" placeholder="输入您的内容..." class="neu-input">
</div>
<div class="neu-toggle-wrapper">
<label class="neu-toggle neu-convex">
<input type="checkbox" id="darkModeToggle">
<span class="neu-slider"></span>
</label>
<span>深色模式</span>
</div>
</div>
<div class="neu-button-group">
<button class="neu-button neu-concave">设置</button>
<button class="neu-button neu-convex active">个人资料</button>
<button class="neu-button neu-concave">通知</button>
</div>
</main>
</div>
</div>JavaScript
document.addEventListener('DOMContentLoaded', () => {
// 按钮组交互
const buttonGroup = document.querySelector('.neu-button-group');
if (buttonGroup) {
buttonGroup.addEventListener('click', (e) => {
if (e.target.classList.contains('neu-button')) {
buttonGroup.querySelectorAll('.neu-button').forEach(btn => {
btn.classList.remove('active');
btn.classList.add('neu-concave');
btn.classList.remove('neu-convex');
});
e.target.classList.add('active');
e.target.classList.remove('neu-concave');
e.target.classList.add('neu-convex');
}
});
}
// 深色模式切换
const darkModeToggle = document.getElementById('darkModeToggle');
if (darkModeToggle) {
darkModeToggle.addEventListener('change', () => {
if (darkModeToggle.checked) {
document.documentElement.style.setProperty('--bg-color', '#333333');
document.documentElement.style.setProperty('--light-shadow', '#424242');
document.documentElement.style.setProperty('--dark-shadow', '#242424');
document.documentElement.style.setProperty('--text-color', '#dddddd');
document.documentElement.style.setProperty('--accent-color', '#64b5f6');
} else {
document.documentElement.style.setProperty('--bg-color', '#e0e0e0');
document.documentElement.style.setProperty('--light-shadow', '#ffffff');
document.documentElement.style.setProperty('--dark-shadow', '#a0a0a0');
document.documentElement.style.setProperty('--text-color', '#6a6a6a');
document.documentElement.style.setProperty('--accent-color', '#3f51b5');
}
});
}
});CSS
.box {
height: 700px;
margin: 0;
padding: 0;
background-color: #e0e0e0;
color: #6a6a6a;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: #e0e0e0;
border-radius: 30px;
padding: 30px;
box-shadow: 10px 10px 20px #a0a0a0,
-10px -10px 20px #ffffff;
display: flex;
flex-direction: column;
gap: 30px;
max-width: 600px;
width: 100%;
}
.neu-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.5em;
padding: 10px 0;
color: #6a6a6a;
}
.neu-header h1 {
font-size: 1.8em;
margin: 0;
font-weight: 600;
}
.neu-header .fas {
cursor: pointer;
padding: 10px;
border-radius: 15px;
transition: all 0.3s ease;
}
.neu-header .fas:active {
box-shadow: inset 10px 10px 20px #a0a0a0,
inset -10px -10px 20px #ffffff;
color: #3f51b5;
}
/* 凸起效果 */
.neu-convex {
background-color: #e0e0e0;
border-radius: 15px;
box-shadow: 10px 10px 20px #a0a0a0,
-10px -10px 20px #ffffff;
transition: all 0.3s ease;
}
/* 凹陷效果 */
.neu-concave {
background-color: #e0e0e0;
border-radius: 15px;
box-shadow: inset 10px 10px 20px #a0a0a0,
inset -10px -10px 20px #ffffff;
transition: all 0.3s ease;
}
.neu-card {
padding: 20px;
text-align: center;
}
.neu-card .fas {
font-size: 3em;
margin-bottom: 15px;
color: #6a6a6a;
}
.neu-card h2 {
font-size: 1.8em;
margin-bottom: 10px;
color: #6a6a6a;
}
.neu-card p {
font-size: 0.9em;
line-height: 1.6;
margin-bottom: 25px;
}
.neu-buttons {
display: flex;
justify-content: center;
gap: 15px;
}
.neu-button {
width: 60px;
height: 60px;
border: none;
outline: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
color: #6a6a6a;
cursor: pointer;
border-radius: 50%;
background-color: #e0e0e0;
}
.neu-button.neu-convex:active {
box-shadow: inset 10px 10px 20px #a0a0a0,
inset -10px -10px 20px #ffffff;
color: #3f51b5;
}
.neu-button.neu-concave:active {
box-shadow: 10px 10px 20px #a0a0a0,
-10px -10px 20px #e0e0e0;
color: #3f51b5;
}
.neu-section {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 20px;
}
.neu-input-wrapper {
flex-grow: 1;
padding: 10px 15px;
}
.neu-input {
width: 100%;
border: none;
outline: none;
background-color: transparent;
color: #6a6a6a;
font-size: 1em;
padding: 5px 0;
}
.neu-input::placeholder {
color: rgba(106, 106, 106, 0.7);
}
.neu-toggle-wrapper {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9em;
}
.neu-toggle {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
cursor: pointer;
}
.neu-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.neu-slider {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 34px;
background-color: #e0e0e0;
box-shadow: 10px 10px 20px #a0a0a0,
-10px -10px 20px #ffffff;
transition: 0.4s;
}
.neu-slider::before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: #e0e0e0;
border-radius: 50%;
box-shadow: inset 2px 2px 5px #a0a0a0,
inset -2px -2px 5px #ffffff;
transition: 0.4s;
}
input:checked + .neu-slider {
background-color: #e0e0e0;
box-shadow: inset 2px 2px 5px #a0a0a0,
inset -2px -2px 5px #ffffff;
}
input:checked + .neu-slider::before {
transform: translateX(26px);
background-color: #3f51b5;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2),
-2px -2px 5px rgba(255, 255, 255, 0.7);
}
.neu-button-group {
display: flex;
background-color: #e0e0e0;
border-radius: 15px;
box-shadow: inset 10px 10px 20px #a0a0a0,
inset -10px -10px 20px #ffffff;
padding: 5px;
}
.neu-button-group .neu-button {
flex: 1;
height: auto;
padding: 10px 15px;
border-radius: 10px;
box-shadow: none;
font-size: 1em;
white-space: nowrap;
}
.neu-button-group .neu-button.active {
box-shadow: 10px 10px 20px #a0a0a0,
-10px -10px 20px #ffffff;
color: #3f51b5;
}
@media (max-width: 600px) {
.container {
padding: 20px;
margin: 10px;
}
.neu-header h1 {
font-size: 1.5em;
}
.neu-card {
padding: 15px;
}
.neu-buttons {
gap: 10px;
}
.neu-button {
width: 50px;
height: 50px;
font-size: 1em;
}
.neu-input-wrapper {
width: 100%;
}
.neu-section {
flex-direction: column;
align-items: flex-start;
}
.neu-button-group {
width: 100%;
}
}常见坑点与注意事项
毛玻璃效果的兼容性
backdrop-filter 在旧版浏览器中不支持,需要添加 -webkit- 前缀并提供降级方案:
.glass {
/* 降级方案:半透明背景 */
background-color: rgba(255, 255, 255, 0.5);
/* 现代浏览器 */
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}新拟物化的可访问性问题
新拟物化设计依赖低对比度颜色,可能影响可读性。建议:
- 为文本增加对比度
- 提供高对比度模式切换
- 使用足够的字号
性能考虑
backdrop-filter 会触发 GPU 加速,在大量元素上使用可能导致性能问题。建议限制毛玻璃元素的数量。
总结
通过这三个案例,你学到了:
- 毛玻璃效果:使用
backdrop-filter: blur()实现,配合多层阴影增加质感 - Liquid Glass:更高的透明度、更强的模糊、增加饱和度参数
- 新拟物化设计:双阴影系统(深 + 浅)模拟凸起和凹陷效果
这些技术可以组合使用,创造出独特的视觉风格。关键是在美观和性能之间找到平衡。
参考资源
- MDN - backdrop-filter
- Neumorphism.io - 新拟物化 CSS 生成器
- CSS Glass Generator - 毛玻璃效果生成器
