马黑黑 发表于 2022-9-26 13:03

JS变通操作伪元素关键帧动画

JS变通操作伪元素关键帧动画 | 马黑黑



由于伪元素在DOM中属于非实体节点,作为擅长操作DOM节点的JS,对伪元素的操作能力极其有限,比如无法直接控制伪元素的关键帧动画。请看下面的CSS和HTML代码:

<style>

#papa {
        margin: auto;
        width: 700px;
        height: 400px;
        display: grid;
        place-items: center;
        position: relative;
}

#papa::before {
        position: absolute;
        content: '';
        width: 100px;
        height: 100px;
        background: olive;
        animation: rot 10s linear infinite;
}

@keyframes rot { to { transform: rotate(360deg); } }

</style>

<div id="papa"></div>

从CSS中可以看到,id="papa" 的 div 盒子有一个 ::before 伪元素,该伪元素运行了 rot 关键帧动画,不停地自转。现在,我们的任务就是要去控制这个动画,即能够暂停动画和继续动画。

对于非伪元素,JS控制动画的方法是通过 style.animationPlayState 来实现,比如,假设 #papa 盒子运行了 rot 动画,则如下指令无障碍实现暂停和继续动画:

papa.style.animationPlayState = 'paused'; //暂停
papa.style.animationPlayState = 'running'; //继续

遗憾的是,对待 #papa 盒子的伪元素,上述指令无能为力,目前也不存在可以直接操纵伪元素关键帧动画的方法。但问题总要解决,也许可以变通一下思路。

CSS可以拥有变量,比如我们给一系列同用一个类选择器的元素预设不同的宽度,我们可以在CSS中声明一个变量 ww(变量名应该自定义):

.ball {
        --ww: 100px;
        width: var(--ww);
}

var(--名称) 就是CSS声明变量的方法,var 是声明关键字,小角括号里的 --名称 是固定结构,符号“--”后面紧跟变量名。CSS变量可以有初始值,--名称: 数值+单位,上述代码中的 --ww: 100px; 就是定义了变量 ww 为 100px。

随后,在JS里,可以动态地给元素设定宽度,例如,我们设置 .ball 系列盒子中的第一个的宽度为120px:

let balls = document.querySelectorAll('.ball'); //获得所有 .ball 元素操作句柄
balls.style.setProperty('--ww','120px');

上面,使用JS内置方法 style.setProperty(属性名,值) 改变了CSS变量 --ww 的初始值,令其宽度等于 120px。

CSS变量不止用于尺寸,还可以用于很多其他方面,关键帧动画的状态乃至于动画的名称,也都是可以的。那么,解决问题的思路就来了:我们在 #papa 盒子中声明一个 --state 变量,由于变量的可继承性,父元素的变量会被子元素所接受,我们通过改变父元素的变量值,达成可间接操纵伪元素的动画的目的。

为此,需要改装一下 #papa 盒子的 CSS 样式表:

#papa {
        --state: running;
        margin: auto;
        width: 700px;
        height: 400px;
        display: grid;
        place-items: center;
        position: relative;
}

和文章开头提供的代码相比,这里的唯一变化是加入了 --state 变量的初始值为 runnig,表示关键帧动画的 play 状态为运行。

::before 伪元素的CSS样式也配套改动:

#papa::before {
        position: absolute;
        content: '';
        width: 100px;
        height: 100px;
        background: olive;
        animation: rot 10s linear infinite;
        animation-play-state: var(--state);
}

仅仅是加了属性 animation-play-state ,其值用变量 var(--state) 表示,var(--state) 将响应来自于父元素的预设值,并将能响应将来JS对此值的动态改变。

为了便于演示,我们需要给HTML代码添加两个按钮,并在JS中处理按钮的点击事件:

<div id="papa">
        <p style="position: absolute; bottom: 20px;">
                <input id="btnpause" type="button" value=" 暂停 " />
                <input id="btnplay" type="button" value=" 继续 " />
        </p>
</div>

JS中,使用 style.setProperty() 方法分别处理 #papa 父盒子的预设变量 --state 的值:

<script>

btnpause.onclick = () => {
        papa.style.setProperty('--state','paused'); //暂停动画
}

btnplay.onclick = () => {
        papa.style.setProperty('--state','running'); //继续动画
}

</script>

效果会怎么样呢?我也没有把握,请移步二楼观看——

马黑黑 发表于 2022-9-26 13:03

<style>
#papa {
        --state: running;
        margin: auto;
        width: 700px;
        height: 400px;
        display: grid;
        place-items: center;
        position: relative;
}
#papa::before {
        position: absolute;
        content: '';
        width: 100px;
        height: 100px;
        background: olive;
        animation: rot 10s linear infinite;
        animation-play-state: var(--state);
}
@keyframes rot { to { transform: rotate(360deg); } }
</style>

<div id="papa">
        <p style="position: absolute; bottom: 20px;">
                <input id="btnpause" type="button" value=" 暂停 " />
                <input id="btnplay" type="button" value=" 继续 " />
        </p>
</div>

<script>
btnpause.onclick = () => {
        papa.style.setProperty('--state','paused');
}

btnplay.onclick = () => {
        papa.style.setProperty('--state','running');
}
</script>

马黑黑 发表于 2022-9-26 13:13

二楼完整代码
<style>
#papa {
        --state: running;
        margin: auto;
        width: 700px;
        height: 400px;
        display: grid;
        place-items: center;
        position: relative;
}
#papa::before {
        position: absolute;
        content: '';
        width: 100px;
        height: 100px;
        background: olive;
        animation: rot 10s linear infinite;
        animation-play-state: var(--state);
}
@keyframes rot { to { transform: rotate(360deg); } }
</style>

<div id="papa">
        <p style="position: absolute; bottom: 20px;">
                <input id="btnpause" type="button" value=" 暂停 " />
                <input id="btnplay" type="button" value=" 继续 " />
        </p>
</div>

<script>
btnpause.onclick = () => {
        papa.style.setProperty('--state','paused');
}

btnplay.onclick = () => {
        papa.style.setProperty('--state','running');
}
</script>

红影 发表于 2022-9-26 13:50

黑黑厉害,思考两三天,到底还是找到了解决JS对伪元素的操作能力的办法,太赞了{:4_199:}

红影 发表于 2022-9-26 13:51

黑黑辛苦了{:4_190:}

梦缘 发表于 2022-9-26 17:24

感谢老师的代码分享,问好!

梦缘 发表于 2022-9-26 17:24

马黑黑 发表于 2022-9-26 13:03
#papa {
        --state: running;
        margin: auto;


这个好,老师辛苦了!{:4_190:}

马黑黑 发表于 2022-9-26 18:12

红影 发表于 2022-9-26 13:50
黑黑厉害,思考两三天,到底还是找到了解决JS对伪元素的操作能力的办法,太赞了

这个是变通方法,原生JS大概只能如此了。

马黑黑 发表于 2022-9-26 18:13

梦缘 发表于 2022-9-26 17:24
这个好,老师辛苦了!

大家都辛苦

马黑黑 发表于 2022-9-26 18:13

梦缘 发表于 2022-9-26 17:24
感谢老师的代码分享,问好!

{:4_190:}

红影 发表于 2022-9-26 19:16

马黑黑 发表于 2022-9-26 18:12
这个是变通方法,原生JS大概只能如此了。

能相出办法来,就很厉害的{:4_187:}

马黑黑 发表于 2022-9-26 19:36

红影 发表于 2022-9-26 19:16
能相出办法来,就很厉害的

这个变通思路 早想到的,只是没时间去鼓捣,今天中午压缩午休时间,匆忙弄出来了

红影 发表于 2022-9-26 19:55

马黑黑 发表于 2022-9-26 19:36
这个变通思路 早想到的,只是没时间去鼓捣,今天中午压缩午休时间,匆忙弄出来了

非常不易,黑黑辛苦了{:4_187:}

马黑黑 发表于 2022-9-26 20:25

红影 发表于 2022-9-26 19:55
非常不易,黑黑辛苦了

想法总是快的,动手总是慢的

红影 发表于 2022-9-27 20:47

马黑黑 发表于 2022-9-26 20:25
想法总是快的,动手总是慢的

这个是个思路实现的过程,很值得留作纪念{:4_187:}

马黑黑 发表于 2022-9-27 21:12

红影 发表于 2022-9-27 20:47
这个是个思路实现的过程,很值得留作纪念

还行,但不必

红影 发表于 2022-9-27 22:00

马黑黑 发表于 2022-9-27 21:12
还行,但不必

嗯嗯,画家不会把自己的画作当回事,其他人会惊艳{:4_173:}

马黑黑 发表于 2022-9-27 22:35

红影 发表于 2022-9-27 22:00
嗯嗯,画家不会把自己的画作当回事,其他人会惊艳

那个我不懂

红影 发表于 2022-9-28 11:14

马黑黑 发表于 2022-9-27 22:35
那个我不懂

自己会作画和自己会写代码一样,自己觉得没什么,其他人会对这些作品非常赞{:4_187:}

马黑黑 发表于 2022-9-28 12:10

红影 发表于 2022-9-28 11:14
自己会作画和自己会写代码一样,自己觉得没什么,其他人会对这些作品非常赞

是吗
页: [1]
查看完整版本: JS变通操作伪元素关键帧动画