使用 Sass 实现 MetaFizzy 效果

Avatar of Kitty Giraudel
Kitty Giraudel 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下文章由 Kitty Giraudel撰写,她是 CSS-Tricks 的常客。遵循 CSS-Tricks 的传统,Hugo 在网络上发现了一个很酷的效果,并深入研究了如何以一种聪明的方式重新创建它。

几天前,我看到了 这个笔,由 Hugo Darby-Brown 创建,旨在使用纯 CSS 和 Sass 的帮助来复制 MetaFizzy 效果,该效果由 David DeSandro 创建。Hugo 做得非常好,并且可能最重要的一个方面是:深入研究原始的 JavaScript 以了解如何在 Sass 中实现它。

他成功了,但我认为他的代码可以改进。即使它明显比普通 CSS 版本好得多,但仍然存在大量重复代码。让我们找到一种方法使其变得超级 DRY!

重要! 这真的只是一个实验。如果您想做类似的事情,请直接使用 JavaScript。CSS 版本大约有 500 行长,并且对 GPU 来说相当繁重。所以,再次强调,这是一个 Sass 实验。仅仅是为了好玩。

我们从哪里开始?

如果 Hugo 没有首先进行演示,我将无法回答这个问题。我们需要做以下事情:

  • 为我们的文本添加一个长阴影,逐渐淡化为黑色
  • 使阴影的颜色随时间缓慢变化
  • 悬停时,变为疯狂的彩虹色
  • 快速动画化这个彩虹

所以最终,我们需要一些东西:

  • 用于平滑阴影的动画
  • 用于悬停状态的动画
  • 悬停状态的颜色列表

就是这样。

平滑阴影

关键帧

平滑阴影是我们将要做的最简单的事情。我们所做的就是输出一堆相同颜色的文本阴影,并将其逐渐淡化为黑色。然后,我们需要一个动画来随时间改变此颜色。hsl() 是完成这项工作的完美选择!

因为我们希望我们的 Sass 代码尽可能 DRY,所以我们将在关键帧中调用一个混合宏,它将为我们处理大量的文本阴影。首先,是框架。

@mixin text-3d($hue) {
  /* Output crazy text-shadows */
}

@keyframes text-3d-animation {
  @for $i from 0 through 10 {
    #{$i * 10%} {
      @include text-3d($i * 36);        
    }
  }
}

我们所做的只是创建一个名为 text-3d-animation 的 CSS 动画,其中包含 11 个显式关键帧(0%10%、… 90%100%)。在每个关键帧中,我们调用一个名为 text-3d 的混合宏,并将 $i * 36 作为 $hue 参数传递(3672108144156、…)。如果您熟悉 hsl() 表示法,您就会明白这是怎么回事。

混合宏

现在我们已经创建了调用混合宏的动画,是时候构建混合宏了!它需要做的就是输出一堆文本阴影。我使用了 50 个,这已经相当多了,但您可以根据需要选择数量(尽管您必须硬编码此值,我没有为此定义参数)。再次强调,我们不会手动编写阴影;Sass 列表和循环就是为此而设计的。

@mixin text-3d($hue) {
  $ts: ();
  @for $i from 1 through 50 {
    $ts: $ts, $i*2px $i*2px hsl($hue + $i*1, 100%, 50% - $i);            
  }
  text-shadow: $ts, 0 0 50px, 0 0 55px;
}

不要惊慌!这实际上很简单。在进入循环之前,我们定义了一个名为 $ts 的空列表(代表文本阴影)。然后我们进入循环。在每次运行中,我们将一个新的阴影添加到我们的列表中,其中:

  • 水平和垂直偏移都设置为 $i * 2px,以使阴影越来越大
  • 我们没有定义任何模糊,但如果您愿意,可以设置一个
  • 颜色在 HSL 中定义,色调设置为给定参数(36 的倍数)+ $i * 1,饱和度为 100%,亮度为 50% - $i,这意味着它逐渐变为黑色

然后,一旦循环最终结束,我们只需将 $ts 列表输出为文本阴影的值。我们还手动添加了两个阴影,用于酷炫的白色“光晕”。

对于非悬停的 MetaFizzy 效果,我们就完成了!它应该可以完美运行。

疯狂的彩虹

在很大程度上,悬停动画的工作方式与非悬停动画相同。我们将像之前一样进行操作,从关键帧开始。

关键帧

@keyframes crazy-rainbow-animation {      
    @for $i from 1 through 50 {
      #{$i * 2%} {
        @include crazy-rainbow($i, tomato yellow green blue purple);
      }
    }
  }

如您所见,这与我们用于 3D 文本动画的内容非常相似,只是我们这里不会使用 11 个显式关键帧,而是 50 个。如果我们想避免出现一个小故障,实际上是 51 个;让我们添加 0% 关键帧(当然是在循环之外)。

@keyframes crazy-rainbow-animation {      
  0% {
    @include crazy-rainbow(50, tomato yellow green blue purple);
  }
  @for $i from 1 through 50 {
    #{$i * 2%} {
      @include crazy-rainbow($i, tomato yellow green blue purple);
    }
  }
}

我们将 crazy-rainbow 混合宏传递两个参数:

  1. $i 作为数值(我们稍后会看到原因)
  2. 我们希望在悬停文本时看到移动的颜色列表(没错,我们可以自定义颜色!)

机制

事情变得复杂了。悬停时的动画基本上看起来像条纹状的阴影(现在已经没有意义了):一种颜色,然后是另一种颜色,然后是另一种颜色,依此类推……但还有更多,颜色正在移动。

其想法是这样的:

@keyframes crazy-rainbow-animation {
  0% {
    text-shadow: 2px 2px   color1, 4px 4px   color1, 6px 6px   color1, 8px 8px   color1,
                 10px 10px color2, 12px 12px color2, 14px 14px color2, 16px 16px color2,
                 18px 18px color3, 20px 20px color3, 22px 22px color3, 24px 24px color3;
  }

  2% {
    text-shadow: 2px 2px   color3, 4px 4px   color1, 6px 6px   color1, 8px 8px   color1,
                 10px 10px color1, 12px 12px color2, 14px 14px color2, 16px 16px color2,
                 18px 18px color2, 20px 20px color3, 22px 22px color3, 24px 24px color3;
  }
  
  /* And so on... */
}

在每个新的关键帧中,颜色(而不是偏移量)必须在列表中移动 1 个索引。列表中的最后一个颜色变为第一个,每个颜色向右移动一个位置。所以最终,我们拥有相同数量的阴影,具有相同的偏移量,除了它们的颜色发生了变化。

构建颜色数组

因此,我们需要一个与我们想要输出的阴影数量一样长的颜色列表。如果我们想要使用 50 个阴影,则需要一个包含 50 种颜色的列表。手动创建此列表将非常麻烦,因此我们为此创建了一个函数。

此函数的目标是将一个颜色列表转换为另一个颜色列表。但是返回的列表应与我们所需的长度匹配,以便我们可以将一个包含 5 种颜色的列表转换为一个包含 50 种颜色的列表。例如:

$given-colors: tomato yellow green blue purple;
$returned-colors: create-list($colors);
/*
$returned-colors: tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato, tomato,
                  yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow, yellow, 
                  green, green, green, green, green, green, green, green, green, green, 
                  blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, 
                  purple, purple, purple, purple, purple, purple, purple, purple, purple, purple;
*/

不幸的是,我意识到 50 个阴影并不总是此动画的最佳数量。我发现,根据您想要运行的颜色数量,动画可能无法完全完成;有时颜色只是“跳跃”。这是因为我们需要最后一个关键帧中的阴影与第一个关键帧中的阴影匹配。

总而言之,我们需要找到一个数字,该数字:

  • 小于或等于 50(关键帧数)
  • 是颜色列表长度的倍数(使动画循环没有任何跳跃)
  • 尽可能接近 50(使动画尽可能流畅)

所以,假设我们有一个包含 6 个元素的列表,则该函数应返回 8(因为 9 将超过 50,因为 9 * 6 = 54)。一个包含 7 个元素的列表应返回 7(因为 7 * 7 = 49)。您应该明白我的意思。

@function define-max($n) {
  @for $i from 1 through 50 {
    @if $i * $n > 50 {
      @return $i - 1;
    }
  }
}

现在回到我们的 create-list() 函数。我们有一个包含一些颜色的列表,并希望将其转换为一个包含大约 50 种颜色的列表?好的,没问题。

@function create-list($colors) {
  $max: define-max( length($colors) );
  $l: ();
  @each $c in $colors {
    @for $i from 1 through $max {
      $l: append($l, $c);
    }
  }
  @return $l;
}

混合宏

好的!到目前为止,我们所做的只是创建了一个函数,用于将颜色列表转换为更长的颜色列表。让我们深入研究混合宏。

@mixin crazy-rainbow($n, $colors) {
  $colors: create-list($colors);
  $ts: (); 
      
  @for $i from 1 through length($colors) {
    $n: if($n > length($colors) or $n == 0, 1, $n);
        
    $ts: $ts, $i*2px $i*2px 0 nth($colors, $n);
        
    $n: $n + 1;
  }
      
  text-shadow: $ts;
}

与之前一样,我们定义一个 $ts 空列表来存储所有阴影。然后我们进入循环,每次在将指针向右移动 1 个索引($n: $n + 1)之前,将阴影添加到 $ts 列表中。如果索引超出列表范围,我们将它移回 1。

它有效!**让我们总结一下**我们做了什么:

  1. 我们根据我们想要运行的颜色数量计算了我们需要输出的阴影数量 (X)。这仅仅是为了使动画循环正确,没有任何视觉故障。
  2. 我们根据之前计算出的数字和我们想要运行的颜色列表生成了一个包含 X 种颜色的巨大数组。
  3. 在每个关键帧中,我们从数组中不同的索引开始输出 X 个阴影。这就是使颜色移动的原因。

改进细节

现在我们已经完成了所有机制,可以稍微改进一下。为什么不创建一个metafizzy 混合宏,为我们的元素分配一些样式呢?

@mixin metafizzy($size, $duration: 10s) {
  font-family: 'MetafizzyLogoRegular', cursive;
  color: white;
  line-height: .9em;
  font-weight: normal;
  font-size: $size;
  animation: text-3d-animation $duration linear infinite;  
  
  &:hover {
    animation: crazy-rainbow-animation 1s linear infinite; 
    animation-direction: reverse; 
  }
}

这个混合宏定义了所有排版内容,包括字体系列(不过你需要字体文件)、字体大小、字体粗细、行高等等。

让我们继续。如何创建一个混合宏来生成我们的两个关键帧动画?我们可以将我们想要在悬停时使用的颜色列表传递给它。

@mixin metafizzy-animations($hover-colors) {
  @keyframes text-3d {
    @for $i from 0 through 10 {
      #{$i*10%} {
        @include text-3d($i * 36); 
      }
    }
  }  

  @keyframes crazy-rainbow {      
    @for $i from 1 through 50 {
      0% { 
       @include crazy-rainbow(50, $hover-colors); 
      } 
      #{$i*2%} {
       @include crazy-rainbow($i, $hover-colors);
      }
    }
  }
}

不幸的是,我们不能将这个混合宏包含在metafizzy 混合宏中,因为后者包含在一个选择器(比如h1)内部。实际上我们可以这样做,但这行不通;@keyframes 动画将输出到选择器内部,而不是文档的根目录。

在 Sass 3.3 中,我们将拥有@at-root 指令,这将使这种事情成为可能(@指令冒泡),但目前这还不可能,所以我们必须将其包含在根目录中。

用法和演示

@include metafizzy-animations(red orangered yellow lightgreen green deepskyblue);

h1 {
  @include metafizzy(25em, 5s);  
  /* Other styles that please you */   
}

完成了。这就是我所有的内容,希望你喜欢,感谢阅读!