hi - blog

一个能气死前端的按钮

卫慧杰
一个能气死前端的按钮

最近在网上刷到一个短视频——怎么用一个按钮气死前端, 设计师与程序员的矛盾就是的这么产生的。

气死前端的按钮

这个夜间模式切换开关效果看着很是炫酷,但确实有难为我们前端人员的嫌疑。

gif

我相信所有前端程序员的心里是

羊驼

万马奔腾·······

不管效果看起来多么复杂,聪明勇敢的前端人总是能想出办法来实现,最近就看到codepen上的大神就把这个切换效果做出来了。纯纯硬核代码完全实现,源代码已在文末整理好。我们来分析一下大神是怎么搞定这个表态的效果的!

动效分析

基于开关的动画效果可以将相关动画步骤进行模块拆分,大概可以拆分为下列模块:

动效分析

首先我们可以将代码拆分为几大模块, 来一一实现。

  • 首先背景是根据开关整个页面背景换为暗色。
  • 开关的内部从太阳变成月亮,圆形由黄变灰。
  • 按钮内部背景色,由蓝变黑。
  • 按钮内部有三个透明度不一的大圆跟随切换
  • 开关状态为白天的时候,会有两朵云彩其中并有个飞机飞过。
  • 开关状态为黑夜的时候,会有星星闪烁,还有宇航员熊飞过。

主要的动效就是这些,具体的还有很多细节,比如开关边缘的阴影效果,状态切换时候的贝塞尔曲线效果,等等,我们这篇文章主要解析核心代码,细节的代码也会在全代码文件里,有兴趣的可以去研究!

需要的知识点

在开始写代码之前我们知道这几个知识点。首先,是以元素以aria- 开头的属性。aria 英文全称:Accessible Rich Internet Application,翻译成中文就是:可访问的富互联网应用程序。其实它是一组属性,定义了使残疾人更容易访问 web 内容和 web 应用程序(尤其是使用JavaScript开发的应用程序)的方法。

这类属性的作用是为了增强网页在残障辅助阅读设备上的识别读取。aria 是一种比较新的辅助访问技术,用来弥补 HTML 和 JS 本身对可访问性方面的不足。支持 aria 并不是必须或者强制的,但是支持 aria 会让你的应用变得更友好,更健壮。

我们会用到,aria-pressedARIA属性,指示按钮的状态打开关闭混合。他可以表示:

  • aria-pressed="false":可以表示按钮为关闭。
  • aria-pressed="true":可以表示按钮为开启
  • aria-pressed="mixed":可以表示按钮为混合状态,

其次是CSS函数 var()此函数用于引用先定义的变量值。语法:var(--variable-name, default-value),--variable-name 表示变量名称,以两个连字符(--)开头,是任何自定义名称。default-value 是可选的,表示在指定变量未定义或无效时要使用的默认值。 举个栗子假设定义 --dark ,当其值为1的时候,最终的代码则是 scale: 1,如果 --dark 没有定义则最终的代码是 scale: 0:

.element {
  scale: var(--dark, 0);
}

这些变量让我们更轻松地维护更新。所以我们可以得到:

BUTTON.setAttribute("aria-pressed", IS_PRESSED ? false : true);

这样我们也就得到--dark 的值,这样也就可以控制整体页面的明暗,颜色等属性。

[aria-pressed=true] {
  --dark: 1;
}

我们写这个按钮会用到很多这个属性,来控制元素的隐藏显示和一些动画效果

硬核代码实现

按钮背景切换

我们开始研究代码的实现首先实现开关内部背景颜色的切换,核心代码如下:

--sky: hsl(204, 53%, 47%);
--night: hsl(229, 25%, 16%);
background: hsl(
  calc(204 + (var(--dark, 0) * 25))
  calc((53 - (var(--dark, 0) * 28)) * 1%)
  calc((47 - (var(--dark, 0) * 31)) * 1%)
);

我们通过--dark 的变化更改 background ,也就是 --sky 和 --night 的值,

(tips:hsl 格式的色值,后面两个参数的值是需要百分比格式的值)

整个页面的背景切换是在按钮点击的时候

就会给body设置 data-dark-mode 属性。

document.body.setAttribute("data-dark-mode", IS_PRESSED ? false : true);

通过 data-dark-mode 得到不同的值。

  --bg: hsl(219, 30%, 88%);
  --color: hsl(219 30% 20%);
}

[data-dark-mode=true] {
  --bg: hsl(219, 30%, 12%);
  --color: hsl(219 30% 98%);
}

按钮内部的背景色切换

北京切换

然后就是按钮内部的太阳转变为月亮,也就是黄色变成灰色。

整个过程我们发现,切换后的灰色

然后是开关内部的圆由黄色(太阳的效果)切换为灰色(月亮的效果)

观察后发现,切换动画中,灰色圆由右至左的覆盖黄色圆, 灰色圆层级高于黄色。

那么我们不难发现,效果的实现方式可以用定位和父子元素,即灰色圆是黄色的子元素。

动画过程:

1)整个圆从左往右平移当 --dark 无定义时最终为0当---dark 为1时,需要平移当前总宽度减去自身圆的宽度的距离,即「3 / 8 * var(--width)」。

translate: calc(
    var(--dark, 0) * (var(--width) - (3 / 8 * var(--width)))
  ) 0;

2)圆内的动画月亮的背景元素向左平移X轴的100%,之后修改X轴为0%。

translate: calc((100 - (var(--dark, 0) * 100)) * 1%) 0%;

再加上过渡动画

transition: translate var(--speed) ease-in-out;

外部的父元素增加了 overflow: hidden,所以默认平移X轴100%是不可见的,大家看下面没有增加 overflow: hidden 代码的效果就更清晰实现的原理了。

gif

背景透明圆的切换动画

这个很简单了radial-gradient 径向渐变绘制三层背景色,掌握好色值渐变开始和结束以及大小。将中心定位于太阳的中心。和太阳做一样的平移动画就可以了。

background:
    radial-gradient(hsl(0 0% 100% / 0.25) 40%, transparent 40.5%),
    radial-gradient(hsl(0 0% 100% / 0.25) 56%, transparent 56.5%)
    hsl(0 0% 100% / 0.25);

星空云朵切换动画

因为动效比较复杂,我们可以用svg来实现,源代码就放在最后的源代码里了,云朵分成两个颜色,所有要有两个SVG的代码,所以

<svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228">
  <g class="clouds">
    <path fill="#D9D9D9" d="M335..." />
  </g>
</svg>

<svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228">
  <g class="clouds">
    <path fill="#fff" d="M328..." />
  </g>
</svg>

白天状态的云朵,并不是左右平移,而是上下,只需改变Y轴的值。

transition: translate var(--speed) var(--easing);
translate: 0 calc(
  var(--dark, 0) * (100% - (3 / 8 * var(--width)))
);

星星闪烁的效果其实就是通svg元素设置缩放动画,可以给不同的星星设置不同的执行时间和延迟执行时间。

animation: twinkle 4s -2s infinite;

@keyframes twinkle {
  0%, 40%, 60%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(0);
  }
}

宇航员 & 小飞机

这两个元素都有动画效果,宇航员一边飘过一边旋转,小飞机也缓缓上升。

// 平移动画
translate: calc(var(--dark, 0) * 400%) calc(var(--dark, 0) * -350%);
transition: translate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * (0.4 * var(--dark, 0))) linear;

// 自身旋转动画
rotate: calc(var(--dark, 0) * 360deg);
transition: rotate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * 0.4) linear, scale var(--speed) ease-in-out;

最后

到了这的我们基本就已经搞定了整个按钮的核心功能,实现了交互效果,但有一些细节的阴影,过渡,本塞尔曲线等没有进行具体的介绍。

Current profile photo
© 2022 hi - blog
京ICP备2022015573号-1