马黑黑 发表于 2025-7-13 12:51

《Matsuri》代码解析

本帖最后由 马黑黑 于 2025-7-13 12:52 编辑 <br /><br /><style>
        .artBox { font-size: 18px; }
        .artBox > p { margin: 10px 0; line-height: 30px; }
        .artBox mark { padding: 4px 6px; background: lightblue; }
        .tRed { color: red; }
        #prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: #eee; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
        #prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
        iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>

<div id="prevBox"></div>
<div class="artBox">
        <p>在<a href="http://mhh.52qingyin.cn/art/show.php?st=4&sd=4&art=mahei_1752289057" target="_blank">《CSS+HTML :金球藏娇》</a>代码演示中,我们仅仅使用CSS+HTML实现了金球开合发现惊喜的交互功能,现在,我们想将其改装并扩展一下:形状弄成蛋形,再给它添加一个简单的粒子系统。设想是这样:蛋壳打开N多个小圆球跑出来绕成一圈、随后转圈圈,蛋壳合上小圆球自动回位——这一切将和音频的播放、暂停联动。下面是代码解析。</p>
        <p>首先,设计好HTML代码结构:</p>
        <div class="hEdiv" data-name="HTML结构代码"><pre class="hEpre">
&lt;div id="papa"&gt;
    &lt;div id="ball"&gt;
      &lt;div id="lzwrap"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
        </pre></div>
        <p>上面的代码并不是帖子的完整HTML代码,我们的目的是通过它了解我们即将创建的粒子系统在HTML目录树结构中的构成。ball是粒子系统的容器元素,它的父元素是papa,其下有一个lzwrap,这是装载粒子的容器。</p>
        <p>接下来解释对应上述标签的CSS样式,代码中必要处都提供有相应注释说明:</p>
        <div class="hEdiv" data-name="CSS代码 :#papa"><pre class="hEpre">
#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; /* 蛋的尺寸 */
}
        </pre></div>
        <p>#papa 选择器中放置了两个CSS变量,--r 和 --size,后面的JS交互管理就只需要针对papa进行操作,注意后续CSS子选择器不要再单独设置变量值,变量的穿透力就得以保持。除了这两个CSS变量,还有 --a1、--a2,用于控制伪元素的开合,初始值为0所以不必要在此赋值,后续的JS会一并接管。</p>
        <div class="hEdiv" data-name="CSS代码 :#ball及其伪元素"><pre class="hEpre">
#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)); /* 旋转角度 :开合需要 */
}
        </pre></div>
        <p>关于 clip-path,我们使用函数 inset 进行矩形切割,其内参数依照<span class="tRed">上右下左</span>的次序切割,支持百分比。以前伪元素为例说明:0 表示上不切割,50%表示右切掉一半,后面两个是0表示下左不切割。后伪元素切掉的是左边,参数原理自己琢磨一下。</p>
        <div class="hEdiv" data-name="CSS代码 :粒子容器和粒子及动画相关"><pre class="hEpre">
/* 粒子容器 */
#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); } }
        </pre></div>
        <p>最后看JS代码:</p>
        <div class="hEdiv" data-name="CSS代码 :#papa"><pre class="hEpre">
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); // 运行全屏切换(含音频联动管理功能)
        </pre></div>
</div>

<script type="module">
        import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
        const pres = document.querySelectorAll('.hEpre');
        const divs = document.querySelectorAll('.hEdiv');
        divs.forEach( (div, key) => hlight.hl(div, pres));
</script>

马黑黑 发表于 2025-7-13 12:51

帖子地址:

Amernan - Matsuri - 动画音画 - 花潮论坛 - Powered by Discuz!

杨帆 发表于 2025-7-13 14:28

辛苦了!谢谢马老师精彩分享、经典解析{:4_190:}

红影 发表于 2025-7-13 15:38

粒子小球的散出去和收拢回来挺不容易懂,是这句吧 --r: ${aud.paused ? 0 : r}vw;
彩蛋也是的,看到了文字解说,音频暂停时 0,否则旋转预定的角度{:4_204:}

红影 发表于 2025-7-13 15:43

--a1 --a2还有--deg --r,在css里要用到,然后都是JS接管并操控的,才有这些动作呢。css里还设置有交互响应时长。css和js的完美配合{:4_199:}

马黑黑 发表于 2025-7-13 17:54

杨帆 发表于 2025-7-13 14:28
辛苦了!谢谢马老师精彩分享、经典解析

{:4_191:}

马黑黑 发表于 2025-7-13 18:00

红影 发表于 2025-7-13 15:38
粒子小球的散出去和收拢回来挺不容易懂,是这句吧 --r: ${aud.paused ? 0 : r}vw;
彩蛋也是的,看到了文字 ...

li-zi {} 选择器里有下面这句:

    transform: rotate(var(--deg)) translate(var(--r));

这句代码设置了 li-zi 标签的转换属性,属性值意思是 旋转 --deg 个角度、平移 --r 个像素。旋转的角度和平移的距离未赋值时都是 0,而后,JS会动态改变这些变量值,这就实时驱动 transform 属性的变化,进而改变 li-zi 元素的位置——我们应该已经知道,元素旋转一定角度后紧跟这平移一定距离会产生圆弧位置移动效果。

马黑黑 发表于 2025-7-13 18:05

红影 发表于 2025-7-13 15:43
--a1 --a2还有--deg --r,在css里要用到,然后都是JS接管并操控的,才有这些动作呢。css里还设置有交互响 ...

JS发明之初就是用来影响Web页的:最早期的Web页没有交互功能,打开了只能看看。显然这不行,于是,Brendan Eich 花了十天时间(一说一个星期)写出了JS,让浏览器直接运行,酱紫,Web页就开始有交互功能了,这是1995年的事情。而后,JS不断被扩展,功能越来越强大。

红影 发表于 2025-7-13 21:32

马黑黑 发表于 2025-7-13 18:05
JS发明之初就是用来影响Web页的:最早期的Web页没有交互功能,打开了只能看看。显然这不行,于是,Brenda ...

JS居然是用这么短时间创建出来的啊,厉害了{:4_199:}

马黑黑 发表于 2025-7-13 21:33

红影 发表于 2025-7-13 21:32
JS居然是用这么短时间创建出来的啊,厉害了

之前说过一次的,你可能没有印象

红影 发表于 2025-7-13 21:36

马黑黑 发表于 2025-7-13 18:00
li-zi {} 选择器里有下面这句:

    transform: rotate(var(--deg)) translate(var(--r));


是的,有旋转后面跟着就平移,会产生圆弧效果。
嗯嗯,谢谢黑黑解说{:4_187:}

马黑黑 发表于 2025-7-13 21:37

红影 发表于 2025-7-13 21:36
是的,有旋转后面跟着就平移,会产生圆弧效果。
嗯嗯,谢谢黑黑解说

还有CSS变量的使用,这是经典所在。

CSS很快会有 if 语句,事实上,现在的 chrome浏览器已经支持了

红影 发表于 2025-7-13 22:50

马黑黑 发表于 2025-7-13 21:33
之前说过一次的,你可能没有印象

嗯嗯,可能没注意{:4_173:}

红影 发表于 2025-7-13 22:50

马黑黑 发表于 2025-7-13 21:37
还有CSS变量的使用,这是经典所在。

CSS很快会有 if 语句,事实上,现在的 chrome浏览器已经支持了

css的功能也越来越扩展了。

杨帆 发表于 2025-7-14 15:28

讲的真好,谢谢马老师经典解析{:4_190:}

马黑黑 发表于 2025-7-14 18:29

杨帆 发表于 2025-7-14 15:28
讲的真好,谢谢马老师经典解析

{:4_191:}
页: [1]
查看完整版本: 《Matsuri》代码解析