前几天我在一个十字路口停下。当时在下雨。路口另一边的路向上倾斜,所以我可以看到停在路口另一边的汽车,就像体育场看台一样。我可以看到它们所有的挡风玻璃雨刮器同时运作,彼此不同步。另外,其中一些看起来像是坏掉的,它们在奇怪的时间和角度拍打着。
这与网页设计和开发有什么关系?实际上没什么关系,只是我从这个场景中获得灵感,创造了一些东西,结果它成为了一个有趣的“技巧”大杂烩。
查看示例 带有奇怪挡风玻璃雨刮器的汽车 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)]
第一个评论者…
太酷了。感谢 Chris。
嗨 Chris,演示在 Safari 上看起来不错。我在使用 Mac;在 Chrome 44.0.2403.89 上,缩放功能无法正常工作,雨刮器也不动。在 Firefox (50.0.2) 上,CodePen.io 本身崩溃了(看起来像是主要的 CSS 问题)。
我恐怕没有看到这些问题。您可以随时联系 CodePen 支持,咨询 CodePen 相关问题。
非常酷
我喜欢你从这样的时刻中获得灵感,但更酷的是你坐下来把它构建出来。
我真的很喜欢这个示例,它启发我去尝试,并做出了这个.
我以前怎么没听说过 Noun Project。这是我几个月来见过的最棒的资源,也许从我开始使用它之后就是了。幸好你所在的城市下了雨,否则我可能永远不会发现它。