在《CSS+HTML :金球藏娇》代码演示中,我们仅仅使用CSS+HTML实现了金球开合发现惊喜的交互功能,现在,我们想将其改装并扩展一下:形状弄成蛋形,再给它添加一个简单的粒子系统。设想是这样:蛋壳打开N多个小圆球跑出来绕成一圈、随后转圈圈,蛋壳合上小圆球自动回位——这一切将和音频的播放、暂停联动。下面是代码解析。
首先,设计好HTML代码结构:
<div id="papa">
<div id="ball">
<div id="lzwrap"></div>
</div>
</div>
上面的代码并不是帖子的完整HTML代码,我们的目的是通过它了解我们即将创建的粒子系统在HTML目录树结构中的构成。ball是粒子系统的容器元素,它的父元素是papa,其下有一个lzwrap,这是装载粒子的容器。
接下来解释对应上述标签的CSS样式,代码中必要处都提供有相应注释说明:
#papa {
margin: 30px 0;
left: 50%;
transform: translateX(-50%);
width: clamp(600px, 90vw, 1400px); /* 自适应显示设备宽度 */
height: auto; /* 高度 :配合下句产生自适应高度效果 */
aspect-ratio: 16/9; /* 保持帖子宽高比例 */
background: url('https://638183.freep.cn/638183/t24/6/matsuri.jpg') no-repeat center/cover;
box-shadow: 2px 2px 8px #000;
z-index: 1; /* 提升在DOM中的层级 :不让浮动按钮遮挡,可以设为更大 */
overflow: hidden;
display: grid; /* 采用网格布局 */
place-items: center; /* 子元素默认绝对居中 */
position: relative;
--r: 2vw; /* 粒子活动半径 */
--size: 10vw; /* 蛋的尺寸 */
}
#papa 选择器中放置了两个CSS变量,--r 和 --size,后面的JS交互管理就只需要针对papa进行操作,注意后续CSS子选择器不要再单独设置变量值,变量的穿透力就得以保持。除了这两个CSS变量,还有 --a1、--a2,用于控制伪元素的开合,初始值为0所以不必要在此赋值,后续的JS会一并接管。
#ball {
position: absolute;
width: var(--size); /* 蛋的宽度 */
height: var(--size); /* 蛋的高度 */
/* 用背景图实现彩蛋藏娇 */
background: url('https://638183.freep.cn/638183/t23/1/rabbit.gif') no-repeat center/cover;
border-radius: 50%;
filter: drop-shadow(10px 10px 40px #eee);
cursor: pointer;
display: grid; /* 网格布局 */
place-items: center; /* 强制子元素(包含其伪元素)居中 */
}
/* 伪元素公用设置 */
#ball::before, #ball::after {
position: absolute;
content: '';
/* 宽高大于父体以支持蛋形设计效果能遮挡主体背景 */
width: 120%;
height: 120%;
background: linear-gradient(35deg, skyblue, lightgreen);
border-radius: 100% 50%; /* 蛋形边框设置 */
transition: 1s; /* 交互响应时长 */
transform-origin: 50% 97%; /* 转换原点 :配套蛋形设计,Y轴不设100% */
}
/* 前伪元素 */
#ball::before {
clip-path: inset(0 50% 0 0); /* 切掉右边 */
transform: rotate(var(--a1)); /* 旋转角度 :开合需要 */
z-index: 90; /* 提升层级 :遮挡ball内子元素需要 */
}
/* 后伪元素 */
#ball::after {
clip-path: inset(0 0 0 50%); /* 切掉左边 */
transform: translate(-1px) rotate(var(--a2)); /* 旋转角度 :开合需要 */
}
关于 clip-path,我们使用函数 inset 进行矩形切割,其内参数依照上右下左的次序切割,支持百分比。以前伪元素为例说明:0 表示上不切割,50%表示右切掉一半,后面两个是0表示下左不切割。后伪元素切掉的是左边,参数原理自己琢磨一下。
/* 粒子容器 */
#lzwrap {
position: absolute;
display: grid; /* 网格布局 */
place-items: center; /* 规定子元素绝对居中 */
}
/* 粒子 */
li-zi {
position: absolute;
width: 50px; /* 宽 */
height: 50px; /* 高 */
border-radius: 50%; /* 圆形 */
/* 线性渐变背景 :颜色由后续JS赋值 */
background: linear-gradient(35deg, var(--c1), var(--c2));
/* 预设路线 :通过旋转+平移实现,变量由后续JS复制 */
transform: rotate(var(--deg)) translate(var(--r));
transition: 0.65s; /* 交互相应时长 */
}
/* 类选择器 :运行关键帧动画,将由JS动态授权给粒子容器 */
.run { animation: run 12s linear infinite; }
/* 关键帧动画 :旋转360度 */
@keyframes run { to { transform: rotate(360deg); } }
最后看JS代码:
import { FS } from 'https://638183.freep.cn/638183/web/js/fullscreen.js'; // 导入全屏切换模块
var total = 12; // 小球数
// 创建小球(粒子)
Array.from({ length: total }).forEach( (lz, idx) => {
lz = document.createElement('li-zi');
lz.style.cssText += `
--c1: #${Math.random().toString(16).substring(2, 8)}; /* 渐变颜色一 */
--c2: #${Math.random().toString(16).substring(2, 8)}; /* 渐变颜色二 */
--deg: ${-idx * 360 / total}deg; /* 旋转角度 */
`;
lzwrap.appendChild(lz); // 添加到粒子容器
});
// 粒子状态
var init = () => {
//变量 r 对应CSS变量 --r,s 对应CSS变量 --size
var r = 15 * papa.clientWidth / 1400,
s = 10 * papa.clientWidth / 1400;
papa.style.cssText += `
--size: ${s}vw;
--r: ${aud.paused ? 0 : r}vw;
/* 下面两行设置彩蛋伪元素翻转角度 */
--a1: ${aud.paused ? 0 : -60}deg; /* 音频暂停时 0,否则时 -60 */
--a2: ${aud.paused ? 0 : 60}deg; /* 音频暂停时 0,否则时 60 */
`;
};
// 关键帧动画管理
var animate = () => aud.paused ? lzwrap.classList.remove('run') : lzwrap.classList.add('run');
// 监听音频标签开始播放和暂停事件
aud.onplaying = aud.onpause = () => {
init();
animate();
};
window.onresize = () => init(); // 窗口自适应
FS(papa, ball); // 运行全屏切换(含音频联动管理功能)