动画单 Div 艺术

Avatar of Dan Wilson
Dan Wilson

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

当你深入挖掘你的工具时,你会惊讶于你能用最基本的 HTML 创建出什么。我一直对 Lynn Fisher 和其他人创作的“单 Div 艺术”印象深刻,他们用一个通用的 <div> 创建了一个美丽的 仙人掌阿拉莫 或熊猫。

查看 CodePen 上 Lynn Fisher 的 #dailycssimages 01: 熊宝宝 (@lynnandtonic)。

由于它们使用背景渐变、阴影、文字阴影等等,并且只有一个 <div> 以及它的 ::before::after 伪元素,所以很难直接跳进去并分析它们是如何制作的。在我们继续之前… 请查看 Lynn Fisher 关于 为什么以及如何开始使用单 Div 艺术 的文章。

div 作品很少做动画。如果你可以变换你的 div 或它的伪元素之一,那是可以的(就像 Lynn Fisher 在她奇妙的 BB-8 中所做的那样)。但是你不能直接改变你 div 内部创建的单个“元素”的 opacitytransform,因为它们不是真正的 DOM 元素。

我坚信尝试一些不同且有趣的东西来学习你可能永远学不到的工具。使用单 div 的限制可能不适合生产工作,但它可以成为一种很好的练习(和挑战),以一种有趣的方式扩展你的技能。本着这种精神,我们将使用这种技术来探索自定义属性(CSS 变量)是如何工作的,甚至为我们在 div 内部进行动画提供了一种途径。为了说明,我们将分解以下使用多种动画方法的示例

查看 CodePen 上 Dan Wilson 的 单 Div 手风琴(使用 CSS 变量进行动画) (@danwilson)。

这把风琴(乐器,而不是 UI 结构)主要由三个部分组成:键盘侧(我们的 div)、风箱(压缩的部分,即 div::before)和按钮侧(div::after)。由于风琴自然地分为这些部分,我们可以使用 CSS 关键帧动画变换每个部分来开始我们的动画。风箱在不同的 scaleX 值之间转换,两侧使用相反的 translateX 值来随着风箱缩放而移动。因此,手风琴就诞生了。

查看 CodePen 上 Dan Wilson 的 单 Div 手风琴分解:变换 (@danwilson)。

使用 CSS 自定义属性组织 <div>

对这三个主要部分进行动画处理并思考它们比思考内部的出现方式更直接。将 div 内部的各个部分进行分组和命名可能会有所帮助,而自定义属性为我们提供了一种原生方法来实现这一点。不是看起来无限的线性渐变色停止的层次结构,而是可以为钢琴键盘定义 -white-keys 和 --black-keys。不是多层渐变的横截面,而是可以定义包含单个 --tea-bag 和相关的 --tea-bag-position--tea-cup

风琴的左侧归结为

background:
    var(--shine),
    var(--shine),
    var(--button-key1, var(--button)),
    var(--button-key2, var(--button)),
    var(--button-key3, var(--button)),
    var(--black-keys),
    var(--white-keys),
    var(--keyboard-base);

这些变量值可能很长(甚至数百行),但由于变量,键盘层次结构的运作方式在概念上更加清晰。

查看 CodePen 上 Dan Wilson 的 单 Div 手风琴分解:键盘 (@danwilson)。

虽然可以使用 Sass 或 Less 来完成同样的操作,但自定义属性允许我们将来修改这些值。现在,我们可以从概念上思考仅仅对我们的 --button-key2 或风琴的装饰性 --shine 进行动画处理。有一些方法可以解决这个问题。

使用 CSS 关键帧动画处理大型属性值

第一种方法是使用 CSS 关键帧动画来改变包含要移动的元素的属性。如果你想改变背景中的某些东西(例如,我们想将“光泽”线的颜色从红色变为蓝色),你可以在 background 属性中设置交换值。基于之前的代码示例

div {
  /* using background definition from earlier */
  --shine: linear-gradient(to right, transparent 29.5%, red 29.5%, red 70.5%, transparent 70.5%);
  --shine-blue: linear-gradient(to right, transparent 29.5%, blue 29.5%, blue 70.5%, transparent 70.5%);
  animation: modify-shine 2000ms infinite alternate ease-in-out;
}
@keyframes modify-shine {
  100% {
    background:
      var(--shine-blue), /*these two replace the original --shine*/
      var(--shine-blue),
      /* the rest of the background remains unchanged */
      var(--button-key1, var(--button)),
      var(--button-key2, var(--button)),
      var(--button-key3, var(--button)),
      var(--black-keys),
      var(--white-keys),
      var(--keyboard-base);
  }
}

这给了我们很多,尤其是在 background 是可动画的(text-shadowbox-shadow 也是如此)的情况下。在这个例子中,会从红色到蓝色进行过渡。

如果你的属性很长,这可能难以维护,但是自定义属性可以帮助我们通过提取不会改变的部分来减少重复。我们可以通过将不需要动画的部分抽象到一个新的变量中来进一步进行抽象——从而导致变量的层次结构

div {
  --static-component: 
    var(--button-key1, var(--button)),
    var(--button-key2, var(--button)),
    var(--button-key3, var(--button)),
    var(--black-keys),
    var(--white-keys),
    var(--keyboard-base);
  background: 
    var(--shine),
    var(--shine),
    var(--static-component); 
}
@keyframes modify-shine {
  100% {
    background:
      var(--shine-blue),
      var(--shine-blue),
      var(--static-component); 
  }
}

手风琴中的三个音符是通过动画处理 text-shadow 来实现的。

查看 CodePen 上 Dan Wilson 的 单 Div 手风琴分解:音符 (@danwilson)。

在 CSS 关键帧中使用自定义属性进行动画处理

改变状态的另一种相关方法是直接在 keyframes 中改变自定义属性。

@keyframes {
  0% {
    --button1-color: var(--color-primary);
  }
  100% {
    --button1-color: var(--color-secondary);
  }
}

自定义属性没有预定义的行为,在使用 var(…) 之前不是有用的属性,因此规范指出改变它的值会导致它在 50% 时 翻转它的值。这是所有不可动画的 CSS 属性的默认行为,这意味着它不会在值之间进行过渡。

你可能已经猜到,由于我已经提到了规范,因此并非所有浏览器都支持这一点。目前,Chrome 和 Opera 才支持。

这将是在跨浏览器支持时获取跳跃状态的快速方法。如果你在 Chrome 或 Opera 中查看,手风琴使用这种方法来对键盘上的按键和右侧的按钮进行动画处理。为了提供一个更小的示例,这里有一个使用这种方法的“像素艺术”示例,其中眼睛和眉毛将在 Blink 浏览器中移动。其他浏览器将很好地回退到静态图像。在很多方面,这将使用最少的代码,但支持程度最低。

查看 CodePen 上 Dan Wilson 的 使用自定义属性进行动画的像素艺术 (@danwilson)。

通过 JavaScript 动画处理自定义属性值

第三种方法是使用 JavaScript 来直接设置新的属性值(或应用具有不同属性值的类)。在其基本形式中,这可能是对 setInterval 的调用,以切换值的开/关状态(对于我们的钢琴,这可能是按键被按下还是未按下)。

var div = document.querySelector('div');
var active = false;
setInterval(function() {
  active = !active;
  div.style.setProperty('--white-key-1', 
    'var(--white-key-color-' + (active ? 'active' : 'default') +')')
}, 1000);

相应的 CSS

div {
  --white-key-1: var(--white-key-color-default);
  --white-key-color-default: #fff;
  --white-key-color-active: #ddd;
  /* And a linear gradient that follows the following pattern */
  background: linear-gradient(to right, var(-white-key-1) 5%, var(--white-key-2) 5%, var(--white-key-2) 10%, ...); 
}

查看 CodePen 上 Dan Wilson 的 单 Div 钢琴键 (@danwilson)。

我们使用 JavaScript 将 white-key-1 设置为 white-key-color-defaultwhite-key-color-active 变量中的值,具体取决于其状态。

这种方法在切换某些东西的开/关状态(例如,直接改变大小、位置或颜色)时很有用。这是手风琴右侧按钮的动画处理方式(在关键帧方法不支持时作为回退)。

查看 CodePen 上 Dan Wilson 的 单 Div 手风琴分解:右侧 (@danwilson)。

九个按钮中的每一个都使用以下默认圆圈,其中 --color1 是浅蓝色,--button-dim 是 1.4vmin

--button: radial-gradient(circle, 
  var(--color1) var(--button-dim), 
  transparent var(--button-dim));

如果我想稍后将特定按钮更改为“按下”状态,我可以在 CSS 中设置一个特定值,例如第四个按钮

--button4: radial-gradient(circle, 
  var(--button4-color, var(--color1)) var(--button4-dim, var(--button-dim)),
  transparent var(--button4-dim, var(--button-dim)));

这个属性类似,但它用特定于该按钮的值替换了--button-dim--color1,这些值与var()中的默认值相结合。这个默认值可以在我们的变量中通过使用var(--my-specific-variable, 13px)的形式指定。我们可以更进一步,甚至使用另一个变量值作为我们的默认值,例如var(--my-specific-variable, var(--my-default-variable))。第二种形式是我们的前面代码示例用来创建第四个按钮的特定定义,同时保持其默认值不变。如果你有一些想要保持不变的按钮,它们可以在不同的background-position中使用默认的--button属性。

在手风琴示例中,--button4-color--button4-dim从未在 CSS 中明确定义。因此,当加载时,它们使用--color1--button-dim的默认值。JS 最终会修改这些值,并创建我们的开/关动画。

var enabled = false;
setInterval(function() {
  enabled = !enabled;
  div.style.setProperty('--button4-dim', enabled ? '1.2vmin' : 'var(--button-dim)');
  div.style.setProperty('--button4-color', enabled ? 'var(--color1alt)' : 'var(--color1)');
}, 1000);

这将使我们获得类似于在关键帧中直接更改自定义属性的行为,在关键帧中,值从一个状态跳到另一个状态,没有过渡。正如我们已经讨论过的,background*-shadow属性是可动画的(也是可过渡的… 不是以高性能的transformopacity的方式… 而是以可以在小范围内使用的可接受的方式…)。

如果我们采用当前的 JS 开/关方法,并将其与 CSS transition(作用于background)结合,我们可以得到过渡而不是跳跃状态。

div {
  transition: background 2000ms ease-in-out;
}

与 requestAnimationFrame 结合

根据您的各个组件的组成方式,可能无法过渡该属性。如果你想移动某个东西,你可能需要考虑使用requestAnimationFrame

我最喜欢的单 Div 之一是 Tricia Katz 的背包。

查看 CodePen 上 Trish Katz(@techxastrish)的 单 Div 背包

我希望那个拉链能来回移动。使用一个自定义属性来表示拉链的x位置,我们可以使用requestAnimationFrame来更改该值,从而使拉链左右移动。

查看 CodePen 上 Dan Wilson(@danwilson)的 使用 CSS 变量进行动画的单 Div 背包

结论

div内部进行动画,有很多方法可以提升你的技能。为了获得最广泛的支持,我们目前无法只依赖 CSS,尽管我们仍然可以走得很远。自定义属性使修改值更加直接,即使我们需要与 JavaScript 结合起来(我们也可以依靠我们的变量命名,以便清楚地知道我们正在更改什么)。

查看 CodePen 上 Dan Wilson(@danwilson)的 单 Div 动画选项

无论何时你想学习 CSS 或 JavaScript 中的新东西… 看看是否能找到一种“单 Div”的方式来学习它。当你需要从概念上将属性分解或对复杂的值进行动画时,自定义属性可以提供一些新的东西。