带有故障挡风玻璃雨刮器的汽车

Avatar of Chris Coyier
Chris Coyier

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

前几天我在一个十字路口停下。当时在下雨。路口另一边的路向上倾斜,所以我可以看到停在路口另一边的汽车,就像体育场看台一样。我可以看到它们所有的挡风玻璃雨刮器同时运作,彼此不同步。另外,其中一些看起来像是坏掉的,它们在奇怪的时间和角度拍打着。

这与网页设计和开发有什么关系?实际上没什么关系,只是我从这个场景中获得灵感,创造了一些东西,结果它成为了一个有趣的“技巧”大杂烩。

查看示例 带有奇怪挡风玻璃雨刮器的汽车 by Chris Coyier (@chriscoyier) on CodePen.

它是 SVG

当您需要一个小形状,例如汽车时,没有什么比得上 Noun Project.

我实际上使用了他们的小型 Mac 应用程序,并将我喜欢的汽车拖到了 Adobe Illustrator 上。然后在挡风玻璃上画了两条小线,作为雨刮器。字面意义上的,笔直的,<line>

重复 SVG

我计划展示一个完整的汽车网格。我本可以将 SVG 代码粘贴到 HTML 中 20 次。但这效率不高,因为它缺乏控制。我认为编程循环是最好的方法。Pug(HTML 预处理器)提供了简单的循环,所以我使用了它。起初,我做了

- svg = '<svg viewBox="0 0 59 45.9" class="car"> ... </svg>'

while cars < 20
  - cars++
  != svg

我认为可以通过使用 :nth-child 选择器来定位汽车的“行”。例如,如果我想选择第 10-15 辆汽车,我可以使用 .car:nth-child(n+11):nth-child(-n+15)。最后,将整行汽车分组在一起,并将其作为整体进行缩放,会更容易。所以

- cols = 0
- rows = 0
- svg = '<svg viewBox="0 0 59 45.9" class="car"> ... </svg>'

while rows < 4
  - rows++
  div.car-row
    - cols = 0
    while cols < 5
      - cols++
        != svg

尺寸

每辆车都有特定的宽高比。请注意 SVG 的 viewBox 属性。我认为最好根据该宽高比来调整它们的大小。我使用像素设置宽高比作为变量,然后可以使用一个乘数来缩放它们。例如,这里我将它们的大小“加倍”

:root {
  --carWidth: 59px;
  --carHeight: 46px;
}
.car {
  width: calc(var(--carWidth) * 2);
  height: calc(var(--carHeight) * 2);
}

在我决定用 div 将汽车的“行”分隔开来之前,我能够通过将 body 的宽度限制为汽车宽度的倍数来强制浮动的汽车排成一行。

动画雨刮器

雨刮器的动画显然是一个旋转变换。通常我会担心 SVG 中的这个问题,因为 SVG 元素上的变换在不同浏览器中的一致性非常差。这就是我使用 GSAP 的原因,它可以规范这一点。

我的第一个想法是设置一个时间轴。GSAP 中的时间轴有一个 yoyo 参数,非常适合挡风玻璃雨刮器的来回式运动。我们将使用旋转,并将其固定在底部右侧,雨刮器在那里枢转。

var wipers = document.querySelectorAll(".wiper");

var tl = new TimelineMax({
  repeat: -1,
  yoyo: true
});
tl.to(wipers, 0.6, {
  rotation: 90, 
  transformOrigin: "bottom right",
  ease: Expo.easeOut,
});

随机化

一个用于输出伪随机数的辅助函数

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

现在我们可以添加随机化,例如延迟和旋转的实际角度

var tl = new TimelineMax({
  repeat: -1,
  yoyo: true,
  delay: getRandomInt(1, 4)
});
tl.to(wipers, 0.6, {
  rotation: function() {
    return getRandomInt(80, 140);
  }, 
  transformOrigin: "bottom right",
  ease: Expo.easeOut,
});

这工作得很好,只是每个雨刮器都有一个它遵循的固定时间轴,它不会随机化每次迭代。我们可以通过循环遍历每个雨刮器,并为每个雨刮器应用一个唯一的时间轴来更接近一点

wipers.forEach(function(el, i) {
  
  var tl = new TimelineMax({
    repeat: -1,
    yoyo: true,
    delay: getRandomInt(1, 4)
  });
  tl.to(el, 0.6, {
    rotation: function() {
      return getRandomInt(80, 140);
    }, 
    transformOrigin: "bottom right",
    ease: Expo.easeOut,
  });
  
});

回调随机化

为了使每次迭代都随机旋转,我认为可能更容易不使用时间轴,而是将单个动画方法作为回调反复调用。这样,每次调用它时,它都可以被随机化。因此,我们将使用 TweenLite 而不是 TimelineMax(),并将它抽象到我们自己的函数中。

function doWiperAnimation(el) {
  TweenLite.to(el, 0.5, {
    delay: getRandomInt(0.1, 0.3),
    rotation: function() {
      return getRandomInt(0, 140);
    }, 
    transformOrigin: "bottom right",
    ease: Power0.easeNone,
    onComplete: function() {
      doWiperAnimation(el);
    }
  });
}

请注意 onComplete 回调如何调用自身。动画循环!我们只需要启动它一次

wipers.forEach(function(el, i) {
  doWiperAnimation(el);
});

您可以随意随机化各种内容,没有限制。以下是如何随机化您选择的缓动方式

var easings = [
  "SlowMo.ease.config(0.7, 0.7, false)",
  "Power0.easeNone",
  "Power2.easeOut"
];

...

ease: easings[Math.floor(Math.random()*easings.length)]