吱吱作响的肖像:使用 CSS path() 函数的乐趣

2025-06-09

吱吱作响的肖像:使用 CSS path() 函数的乐趣

随着 Chrome 88 的发布,我们获得了对 的支持clip-path: path()。这意味着它现在得到了“大多数”主流浏览器的支持!

有了path(),我们就可以为 a 使用路径定义clip-path(了解一下clip-path这里的内容)。这些路径定义字符串与我们在 SVG 路径元素中使用的字符串相同。它的妙处在于,它提供了一种创建形状的方法,而以前这些形状可能只能通过 SVG 来实现。我们甚至可以创建不规则的路径,而无需任何技巧。

随着支持的增加,我们有机会尝试一些有趣的东西了!让我们制作“吱吱作响的肖像”!一个有趣的尝试,clip-path: path()将元素的可视区域裁剪成这些“尼克儿童频道风格”的图案。

创建路径

首先,我们需要自定义 SVG 样式的路径定义字符串。在本例中,我们需要多个。巧妙之处在于clip-path,我们可以用 CSS 来过渡它们。只要clip-path功能和节点数量一致,就可以进行过渡。

要创建一些路径,我们可以跳转到任何矢量图形编辑器。在本例中,我使用的是 Figma。我们不用从头开始创建路径,而是可以使用所需的“splat”作为基础。这个看起来不错!

在线找到的 Splat 示例

这里的技巧是在基础碎片的基础上创建更多碎片。而且,我们需要在不添加或移除任何节点的情况下完成此操作。这是我想到的三个碎片。不过,只要遵循这个规则,你可以创建任何你喜欢的形状!

用一个 Splat 构建三个不同的 Splat

你可能会注意到,第三个 splat 有两个与主形状分离的斑点。这没问题。因为 SVG 路径定义允许我们这样做。我们可以开始一条线,然后闭合它,最后移动到另一点开始另一条线。

但我不是说过它们需要一致的点数吗?确实如此。而且确实如此!这两个斑点出现在每个splat中。不过,诀窍在于,当不需要它们时,我们可以将它们移到路径的其余部分后面。

Figma 在主路径后面显示两个斑点

一旦我们有了 splats,我们就可以导出它们并获取路径定义字符串。

应用 Splats

为了应用 splats,我们将为每条路径创建变量。

.portrait {
  --splat: "M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919 180.294...";
  --splattier: "M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919...";
  --splatted: "M232.5 256C225 251 209.5 262.5 224 281.5C232.736 292.948...";
}
Enter fullscreen mode Exit fullscreen mode

这些是我们直接从导出的 SVG 中提取的路径。

我们打算用“splat”、“splattier”和“splatted”来命名。命名真难。哈哈!不过,我们以“splatted”这个SVG为例。

<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M232.5 256C225 251 209.5 262.5 224 281.5C232.736 292.948 238.561 297.756 251 290.5C257 287 256.114 271.924 250 264.5C243.886 257.076 240 261 232.5 256ZM147 92.5C118.738 94.6708 118 17 93 44C68 71 123.543 76.5 108 101.5C90.5 115 70.81 98.3664 64 115C56.7766 132.643 91.1967 136.948 90.5 156C89.4406 184.974 19.1766 161.499 24.5 190C29.9178 219.006 78.6461 172.635 100 193C130.207 221.808 1 248.5 58.5 291.5C94.5576 318.465 114.991 206.551 140.5 211C183.5 218.5 134.5 294 186.5 279.5C207.5 273 174.638 224.658 196 204C223.117 177.777 275.916 253 291.5 218.5C311.375 174.5 228.698 194.565 224 160C219.553 127.282 291.5 123.5 267.5 87.5C238.5 57 247 125.5 196 105.5C147 92.5 229.5 13.5 173.5 2.5C140.5 2.49999 183.532 89.694 147 92.5ZM45 92.5C36.8766 80.3149 14.1234 75.3149 6.00001 87.5C0.584412 95.6234 2.00001 120.357 14.5 115C27.9606 109.231 36.8766 124.685 45 112.5C50.4156 104.377 50.4156 100.623 45 92.5Z" fill="#A91CFF"/>
</svg>
Enter fullscreen mode Exit fullscreen mode

d我们从元素中提取属性path,并为其创建 CSS 变量。接下来,我们需要一个元素来应用这些属性。让我们创建一个带有“portrait”类的元素。

<div class="portrait"></div>
Enter fullscreen mode Exit fullscreen mode

接下来,对其应用一些样式:

.portrait {
  --splat: "M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919 180.294...";
  --splattier: "M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919...";
  --splatted: "M232.5 256C225 251 209.5 262.5 224 281.5C232.736 292.948...";
  --none: "";
  height: 300px;
  width: 300px;
  background: #daa3f5;
  clip-path: path(var(--clip, var(--none)));
  transition: clip-path 0.2s;
}
Enter fullscreen mode Exit fullscreen mode

这是一个演示,您可以在不同的剪辑状态之间切换:

注意形状如何在三个扁平形状之间过渡。同时,也请注意我们如何为元素指定明确的高度和宽度。此尺寸与我们导出的 SVG 文件的尺寸相匹配。这一点很重要。这是使用 的一个缺点clip-path: path():它缺乏响应性。路径定义是相对于元素尺寸的。这与 CSS 运动路径面临的问题相同。

如果我们能留意裁剪对象的大小,这样做是没问题的。我们也可以针对不同的视口大小创建不同的路径变量。但是,如果你的图片可以动态调整大小,那么使用 SVG 的其他解决方案会更加健壮。

相互作用

在我们的演示中,我们希望 splat 具有交互性。我们可以仅使用 CSS 来实现这一点。我们可以使用作用域 CSS 变量--clip来控制当前剪辑。然后,我们可以在:hover和 上更新该变量:active--active当我们按下指针时,就会触发该状态。

.portrait {
  clip-path: path(var(--clip, var(--splat)));
}
.portrait:hover {
  --clip: var(--splattier);
}
.portrait:active {
  --clip: var(--splatted);
}
Enter fullscreen mode Exit fullscreen mode

把它们放在一起,我们得到了类似这样的效果。试着将鼠标悬停在图标上并按下它。

添加一些角色

现在我们可以对 Splat 进行过渡了,它还需要一些额外的操作。如果我们也对 Splat 进行这些状态的变换呢?

.portrait {
  transition: clip-path 0.2s, transform 0.2s;
  transform: scale(var(--scale, 1)) rotate(var(--rotate, 0deg));
}
.portrait:hover {
  --scale: 1.15;
  --rotate: 30deg;
}
.portrait:active {
  --scale: 0.85;
  --rotate: -10deg;
}
Enter fullscreen mode Exit fullscreen mode

使用作用域 CSS 变量来应用transform,我们可以添加一些内容。这里我们更新了splat 的scaleand rotation。我们可以尝试不同的值,并尝试不同的效果。稍微平移一下元素看起来会不会更好?

添加肖像。

现在说点有意思的事!我不建议用这些我的照片。不过,如果你想用的话,也可以!我有个主意,拍三张自己搞笑的照片,然后让它们回复用户。我找了些人帮忙,最后得到了这三张照片。

三个傻傻的姿势

然后我们需要把它们放入肖像中。

<div class="portrait">
  <img class="portrait__img" src="/me--standing.png" alt="Me"/>
  <img class="portrait__img" src="/me--noticing.png" alt="Me"/>
  <img class="portrait__img" src="/me--falling.png" alt="Me"/>
</div>
Enter fullscreen mode Exit fullscreen mode

那看起来不太好。他们需要一些风格。

.portrait {
  position: relative;
}
.portrait__img {
  height: 100%;
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
Enter fullscreen mode Exit fullscreen mode

几乎。

我们如何在 和 上显示和隐藏它们:hover:active这有点冗长,但我们可以使用nth-of-typedisplay: none

.portrait__img {
  display: none;
}
.portrait__img:nth-of-type(1) {
  display: block;
}
.portrait:hover .portrait__img:nth-of-type(1),
.portrait:hover .portrait__img:nth-of-type(3) {
  display: none;
}
.portrait:hover .portrait__img:nth-of-type(2) {
  display: block;
}
.portrait:active .portrait__img:nth-of-type(1),
.portrait:active .portrait__img:nth-of-type(2) {
  display: none;
}
.portrait:active .portrait__img:nth-of-type(3) {
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

为什么不重构这些样式并将它们分组呢?这样会导致层叠问题,达不到我们想要的效果。

视差图标

差不多了,但看起来有点平淡。如果能加个图标,就能做出基本的视差效果。就用这个吧。

代码括号图标

这里的技巧是使用图像作为元素的背景,但调整其大小以便与之平铺background-repeat

.portrait {
  background-image: url("/code-icon.svg");
  background-color: hsl(10, 80%, 70%);
}
Enter fullscreen mode Exit fullscreen mode

整洁的。

但是,我们想要视差!为了获得视差效果,我们可以background-position根据指针的移动来更新。并且,我们可以将指针位置映射到我们定义的某个限制上。

让我们首先创建一个实用程序,它可以为我们生成一个映射函数。返回的函数将返回一个范围的值映射到另一个范围的结果。

const genMapper = (inputLower, inputUpper, outputLower, outputUpper) => {
  const inputRange = inputUpper - inputLower
  const outputRange = outputUpper - outputLower
  const MAP = input => outputLower + (((input - inputLower) / inputRange) * outputRange || 0)
  return MAP
}
Enter fullscreen mode Exit fullscreen mode

花点时间理解一下这里发生了什么。例如,如果我们的输入范围是0500,输出范围是0100。那么用 调用返回函数的结果会是什么呢250?答案是50

// Generate a function
genMapper(0, 500, 0, 100)
// Returns a function by going through these steps
const inputRange = 500
const outputRange = 100
const MAP => input => 0 + (((input - 0) / 500) * 100)
// If our input value is 250
(250 / 500) * 100
0.5 * 100
// The result!
50
Enter fullscreen mode Exit fullscreen mode

一旦我们有了生成映射函数的效用函数,我们就需要一个与之配合使用的极限。并且,我们需要为水平轴和垂直轴分别生成一个映射器。

const LIMIT = 25 // The amount our icons can move in pixels in either direction
const getX = genMapper(0, window.innerWidth, -LIMIT, LIMIT)
const getY = genMapper(0, window.innerHeight, -LIMIT, LIMIT)
Enter fullscreen mode Exit fullscreen mode

最后一部分是将其绑定到事件监听器。我们从事件中解构xy值,并在 Portrait 元素上设置 CSS 变量。该值来自将xy传入相应的映射函数。

const PORTRAIT = document.querySelector('.portrait')
document.addEventListener('pointermove', ({ x, y }) => {
  PORTRAIT.style.setProperty('--x', getX(x))
  PORTRAIT.style.setProperty('--y', getY(y))
})
Enter fullscreen mode Exit fullscreen mode

现在我们有了视差图标!

吱吱声

最后一点。就在标题里。我们需要一些吱吱声。我通常在freesound.org之类的网站上找音频。不过,你可以在任何地方找到它们,如果你愿意,甚至可以自己录制。

创建一个可以引用的对象并不是一个坏主意Audio

const AUDIO = {
  IN: new Audio('/squeak-in.mp3'),
  OUT: new Audio('/squeak-out.mp3'),
}
Enter fullscreen mode Exit fullscreen mode

然后,要播放音频片段,我们需要做的就是

AUDIO.IN.play()
Enter fullscreen mode Exit fullscreen mode

我们需要将它与我们的肖像结合起来。我们可以在这里使用pointerdownpointerup事件——这样的想法是,当我们按下时,我们会发出一个吱吱声,当我们松开时,我们会发出另一个吱吱声。

如果用户快速连续多次点击肖像,可能会产生不良效果。诀窍在于播放所需的声音,同时停止另一个声音。要“停止”一段Audio,我们可以暂停它,并将 设置currentTime0

PORTRAIT.addEventListener('pointerdown', () => {
  AUDIO.OUT.pause()
  AUDIO.IN.currentTime = AUDIO.OUT.currentTime = 0
  AUDIO.IN.play()
})
PORTRAIT.addEventListener('pointerup', () => {
  AUDIO.IN.pause()
  AUDIO.IN.currentTime = AUDIO.OUT.currentTime = 0
  AUDIO.OUT.play()
})
Enter fullscreen mode Exit fullscreen mode

这样我们就得到了一幅“吱吱作响的肖像”!

就是这样!

这就是制作“吱吱作响的肖像”的方法。但这里真正可行的做法是在尝试新事物的同时享受乐趣。

我本来可以变形几个形状就此打住。但是,为什么要止步于此呢?为什么不想想办法,玩得开心点呢?这倒是个尝试新事物、探索新技巧的好方法。

我们:

  • 创建剪辑
  • 通过过渡来改变它们
  • 制作交互式图像
  • 添加音频
  • 使用映射实用程序创建视差

你能用它做什么clip-path: path()?你的“吱吱作响的肖像”会是什么样子?它可以呈现出完全不同的效果。我很想看看你做了什么!

一如既往,感谢您的阅读。想了解更多?快来Twitter上关注我,或者观看直播

附言:如果你想获取所有代码,可以参考这个CodePen 合集

保持精彩!ʕ •ᴥ•ʔ

鏂囩珷鏉ユ簮锛�https://dev.to/jh3y/squeaky-portraits-having-fun-with-the-css-path-function-2nl0
PREV
我的解决方案是最好的!
NEXT
使用无服务器绘制你的 Github 个人资料