纯CSS实现随机效果

发布于 大漠

最近在Codepen上看到了@Adir写的随机翻牌找蛋蛋(可以想象是砸金蛋)效果,让我再次刷新了对CSS的认知。看到这两个效果之后我才知道,在CSS中除了可以实现 动态计算逻辑运算状态切换 之外,也可以使用纯CSS实现随机效果。你是否和我一样,听到纯CSS实现随机效果有点不可思议,但这是事实,在CSS中,我们可以借助CSS动画的能力来模拟部分随机效果。如果你也想了解CSS如何实现随机效果的话,那阅读这篇文章你将知道其中的原委。

来自其他语言的随机性

众所周知,CSS是一种声明式的标记语言。在很多同学的认知中,它是静态的,即 在CSS中一旦设置了一个值(给CSS属性设置了值),就不能改变。更不能像其他编程语言,做一些复杂的事情,比如逻辑运算、动态计算、状态切换,甚至是更复杂的事情,比如随机效果。

注意,随着CSS的自定义属性calc()函数的出现,虽然我们可以借助这些特性模拟动态计算逻辑运算状态切换等,但这些和其他编程语言的同类特性相比还是有很大的局限性或者说缺陷。

大家都知道,像生成随机数(在运行时生成随机数)是JavaScript这样程序语言领域的知识,即,使用Math.random()可以随机生成数字

Math.random函数生成随机数

抛开程序语言不说,在一些CSS的处理器,比如Sass,他也提供了一个random()函数,可以实现一些随机效果。比如:

// SCSS
.element {
    width: random(80) + vw;
    height: random(50) + vmin;
}

第一次编译出来的结果是:

/* CSS */
.element {
    width: 30vw;
    height: 41vmin;
}

第二次编译出来的结果可能就是:

/* CSS */
.element {
    width: 38vw;
    height: 23vmin;
}

也正因如此,在使用Sass(或SCSS)编码时,有的时候会借助该特性(random()随机函数)实现一些随机效果,比如下面这个随机的渐变效果:

如果使用Sassmeister这样的Sass Playground可以看到每次编译后的结果:

Sass的随机性

从上面的录屏结果不难发现,使用Sass这样的CSS处理器生成随机值,但一旦CSS代码被编译或导出,这些值就会被固定,它的随机性也就被丢失了。其中的原委@Jake Albaugh做过相应的解释:

random in sass is like randomly choosing the name of a main character in a story. it's only random when written. it doesn't change. -- by @Jake Albaugh

大致意思就是“Sass中的随机就像在故事中随机选择一个主角的名字一样,只有在写的时候才是随机的,它不会改变”。

它不像JavaScript中的随机数(如Math.random()函数),这个随机数是在JavaScript运行时产生的。

不过,CSS自定义属性出现之后,我们要以不使用JavaScript的Math.random()在运行时产生随机数,也可以不使用像Sass这样折CSS处理器,在编译时使用random()函数生成随机值,可以直接使用CSS自定义特性(CSS变量)来获得一些“动态随机化”。但是使用CSS自定义属性实现动态随机化并不是纯CSS,因为它们需要使用JavaScript来更新CSS变量(随机生成变量)。

CSS自定义属性+JavaScript实现随机性

在介绍纯CSS实现随机性之前,先来看CSS自定义属性和JavaScript的结合是怎么实现随机性。我们通过一个简单地示例来阐述,CSS自定义属性和JavaScript如何结合起来实现随机性。

先上示例效果:

这个示例就是随机性给animation-duration设置一个值:

代码很简单:

:root {
    --animation-duration: 2s;
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.circle:nth-child(1) {
    animation: fadeIn var(--animation-duration) ease alternate infinite;
}

每点击button--animation-duration的值都会是一个随机值,那是因为其值是JavaScript的Math.random() + .5

const time = Math.random() + 0.5;

并且使用style.setProperty--animation-duration赋值:

button.addEventListener("click", () => {
    const time = Math.random() + 0.5;
    console.log(time);
    rootEle.style.setProperty("--animation-duration", `${time}s`);
});

这样就把Math.random()生成的随机数运用到动画中,即--animation-duration

上面的示例向大家展示了,JavaScript和CSS的结合可以在运行时产生一个随机的animation-duration值,因此,你每点击一次button,动效都会有所不同。这是因为animation-duration的值不同。不过,我们可以让事情变得更简单一些,那就是可以使用JavaScript随时更新--animation-duration的值。比如下面这个示例,使用JavaScript的setInterval()函数,每隔1s就更新--animation-duration的值:

const rootEle = document.documentElement;

function setProperty(duration) {
    rootEle.style.setProperty("--animation-duratio", `${duration}s`);
}

function changeAnimationTime() {
    const animationDuration = Math.random();
    console.log(animationDuration);
    setProperty(animationDuration);
}

setInterval(changeAnimationTime, 1000);

如果打开浏览器的开发者调试工具,可以看到每隔1s--animation-duration的值会是一个随机产生的值:

看到这里,你可能会说,都使用JavaScript了,用不用CSS自定义属性,也并不是我们所说的纯CSS完成的。换句话说,即使没有CSS自定义属性,我们使用JavaScript操作DOM也可以达到相似的效果,比如:

document.querySelectorAll('.circle')[0].style.animationDuration = `${Math.random()}s`

嗯,这样做是和CSS自定义属性没太大关系,你说的也是有道理的,都用了JavaScript了,那和我们所要追求的目标就不一致了:使用纯CSS实现随机性效果

不过,上面这些介绍,让大家对其他语言中实现随机性有一个初步了解。那接下来,进入到我们今天真正想要了解或学习的知识,不使用任何JavaScript、CSS处理器(比如Sass),来实现随机性效果。

为了更好的展示和阐述,接下来的内容将围绕@Adir SL提供的示例来介绍:

尝试着点击扑克牌,每次亮出的结果都不同(一个随机的结果):

纯CSS实现随机效果

在CSS中不像JavaScript,没有Math.random()生成随机数,也没有像Sass处理器,使用random()在编译时产生随机值,但我们在CSS中可以通过使用一种复杂的动画形式来模拟随机效果。

正如上面的翻牌示例,每次点击都可以看到不同的牌,即在52张牌中随机显示一张:

如果你阅读了上面的示例代码会发现,示例中运用了一个CSS实现的Sprites动效(类似《重新创建Twitter点赞动效》介绍的一种动效)。简单地说,就是通过steps()函数(animation-timing-functiontransition-timing-function中的一种缓动函数),让52张拼装起来的扑克牌能按帧播放。比如:

.poker {
    height: 40vmin;
    width: calc(40vmin / 1.4);
    background-image: url(https://assets.codepen.io/2722301/cards.jpg);
    background-size: cover;
    animation: poker 52s steps(51, end) infinite;
}

@keyframes poker {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: 100% 0;
    }
}

这里非常关键的一点就是CSS的animation中的缓动函数steps(),如果你未接触或者对其不是非常了解的话,那建议花点时间阅读@dancwilson的《Jumps: The New Steps() in Web Animation》一文。

试想一下,如果我们突然让动效停止播放会是什么样的情景呢?想还不如做,我们在上面的示例的基础上添加一个交互行为,让动画在播放和停止之间切换:

// JavaScript
const button = document.querySelector("button");
const poker = document.querySelector(".poker");

button.addEventListener("click", () => {
    poker.classList.toggle("stop");
});

/* CSS */
.poker.stop {
    animation-play-state: paused;
}

添加的代码很简单,当用户点击按钮的时候,poker会添加(或删除)stop类名,来控制动画的播放状态,即animation-play-statepausedrunning之间切换。也就是控制动画播放还是停止。

看到这个效果,是不是有点感觉了。示例中的poker动画,要用52s才能把整个52张牌从左到右播放完,简单地说,花52s完成所有52种状态,而且会不断的循环。而且你点击按钮的时候,动画的animation-play-state的值换成了paused(动画停止播放)。就在这个动画停止的状态,相当于我们随机翻出了一张牌,看上去就是随机出现了一个值。

如果我们把动画的持续时间从52s减到1s,即只用1s钟就循环完成了扑克牌所有状态(52种状态):

.poker {
    animation: poker 1s steps(51, end) infinite;
}

这个时候是不是更真实一点了:

文章开头的原理就是这么的简单,在这个基础上添加一些额外的效果就达到了文章开头示例效果:

有关于示例怎么实现的详细介绍,可以阅读 @Adir SL 的《Creating randomness with pure CSS》一文。

随机翻牌的效果相对来说要简单一点,至少比下面这个小鸡下蛋效果要简单,但他们的原理是相同的:

除了上面介绍的方法(即改变animation-play-status的值)之外,还可以像@Alvaro Montoro 在《Are There Random Numbers in CSS?》文章介绍的方法,通过改变animation-delay的值来实现。比如下面这个示例效果:

如果你敢想的话,还可以用类似的原理做一些其他的效果,比如 @Amin moslemi 写的随机掷骰子效果

未来真的可以在CSS中使用随机函数

在《图解CSS: CSS中的函数》一文中我们专门介绍了分布在不同功能模块的中的CSS函数:

其中特别提到过,在未来不久,在CSS中除了我们熟悉的calc()这样的计算函数之外,后面会有一些像sin()cos()tan()这样的三角函数。既然如此,我哪一天在CSS中也会有一个真正的随机函数random()。虽然到这一天需要一个漫长的过程,但总是会有那么一天的到来。我们一起期待和努力吧。