花哨的图像装饰:蒙版和高级悬停效果

Avatar of Temani Afif
Temani Afif

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

欢迎来到这个三部分系列的第二部分!我们仍在装饰图像,而无需任何额外的元素和伪元素。我希望您已经花时间消化了 第一部分,因为我们将继续使用大量渐变来创建很棒的视觉效果。我们还将介绍 CSS mask 属性以实现更复杂的装饰和悬停效果。

花哨的图像装饰系列

让我们转向我们一起处理的第一个示例……

邮票

信不信由你,制作邮票 CSS 效果只需要两个渐变和一个滤镜

img {
  --r: 10px; /* control the radius of the circles */
  padding: calc(2 * var(--r));
  filter: grayscale(.4);
  background: 
    radial-gradient(var(--r),#0000 98%,#fff) round
      calc(-1.5 * var(--r)) calc(-1.5 * var(--r)) / calc(3 * var(--r)) calc(3 * var(--r)),
    linear-gradient(#fff 0 0) no-repeat
      50% / calc(100% - 3 * var(--r)) calc(100% - 3 * var(--r));
}

正如我们在 上一篇文章 中看到的,第一步是使用 padding 在图像周围留出空间,以便我们绘制背景渐变并在那里看到它。然后,我们结合使用 radial-gradient()linear-gradient() 来裁剪图像周围的圆圈。

这是一个逐步说明,展示了渐变是如何配置的

请注意第二步中使用了 round 值。这对于技巧来说非常重要,因为它确保渐变的大小调整为完美地对齐所有边,无论图像宽度或高度是多少。

来自 规范:图像会重复尽可能多地填充背景定位区域。如果它不能完全容纳整数次,则会重新缩放以使其能够容纳。

圆形边框

让我们看看另一个使用圆圈的图像装饰……

此示例也使用 radial-gradient(),但这次我在图像周围创建了圆圈,而不是剪切效果。请注意,我再次使用了 round 值。这里最棘手的地方是边框和图像之间的透明间隙,这就是我使用 CSS mask 属性的地方

img {
  --s: 20px; /* size of the frame */
  --g: 10px; /* the gap */
  --c: #FA6900; 

  padding: calc(var(--g) + var(--s));
  background: 
    radial-gradient(farthest-side, var(--c) 97%, #0000) 
      0 0 / calc(2 * var(--s)) calc(2 * var(--s)) round;
  mask:
    conic-gradient(from 90deg at calc(2 * var(--s)) calc(2 * var(--s)), #0000 25%, #000 0)
      calc(-1 * var(--s)) calc(-1 * var(--s)),
    linear-gradient(#000 0 0) content-box;
}

蒙版允许我们显示图像的区域——这要归功于其中的 linear-gradient()——以及它每一侧周围的 20px——这要归功于 conic-gradient()20px 仅仅是定义边框大小的变量 --s。换句话说,我们需要隐藏间隙。

我的意思是这个

线性渐变是背景的蓝色部分,而圆锥渐变是背景的红色部分。这两个渐变之间的透明部分就是我们从元素中裁剪的部分,以创造内部透明边框的错觉。

内部透明边框

对于这个,我们不会创建边框,而是尝试一些不同的东西。我们将尝试在图像内部创建透明的内部边框。在现实世界的场景中可能不是那么有用,但对于 CSS 蒙版来说是一个很好的练习。

与前面的示例类似,我们将依靠两个渐变:一个用于内部部分的 linear-gradient() 和一个用于外部部分的 conic-gradient()。我们将在它们之间留出空间来创建透明边框效果。

img {
  --b: 5px;  /* the border thickness */
  --d: 20px; /* the distance from the edge */

  --_g: calc(100% - 2 * (var(--d) + var(--b)));
  mask:
    conic-gradient(from 90deg at var(--d) var(--d), #0000 25%, #000 0)
      0 0 / calc(100% - var(--d)) calc(100% - var(--d)),
    linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat;
}
Detailing the parts of the image that correspond to CSS variables.

您可能已经注意到,此示例的圆锥渐变与上一个示例的语法不同。两者都应该创建相同的形状,那么为什么它们会不同呢?这是因为我们可以使用不同的语法来达到相同的结果。这乍一看可能令人困惑,但这是一个很好的特性。您不必找到实现特定形状的唯一解决方案。您只需要从众多可能性中找到一个适合您的解决方案即可。

以下是使用渐变创建外部正方形的四种方法

还有更多方法可以实现这一点,但您明白了。

没有最好的™方法。就我个人而言,我尝试找到代码最短且最优化的那个。对我来说,任何需要更少渐变、更少计算和更少重复值的解决方案都是最合适的。有时我选择更冗长的语法,因为它给了我更多灵活性来更改变量和修改内容。这需要经验和实践。您玩得越多渐变,您就越了解何时使用哪种语法。

让我们回到我们的内部透明边框并深入研究悬停效果。如果您没有注意到,有一个很酷的悬停效果,它使用 font-size 技巧移动该透明边框。这个想法是使用 1em 的值定义 --d 变量。此变量控制边框距边缘的距离。我们可以像这样转换

--_d: calc(var(--d) + var(--s) * 1em)

…为我们提供以下更新的 CSS

img {
  --b: 5px;  /* the border thickness */
  --d: 20px; /* the distance from the edge */
  --o: 15px; /* the offset on hover */
  --s: 1;    /* the direction of the hover effect (+1 or -1)*/

  --_d: calc(var(--d) + var(--s) * 1em);
  --_g: calc(100% - 2 * (var(--_d) + var(--b)));
  mask:
    conic-gradient(from 90deg at var(--_d) var(--_d), #0000 25%, #000 0)
     0 0 / calc(100% - var(--_d)) calc(100% - var(--_d)),
    linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat;
  font-size: 0;
  transition: .35s;
}
img:hover {
  font-size: var(--o);
}

font-size 最初等于 0,因此 1em 也等于 0,并且 --_d 等于 --d。但是,在悬停时,font-size 等于由 --o 变量定义的值,该变量设置边框的偏移量。这反过来更新了 --_d 变量,通过偏移量移动边框。然后我添加另一个变量 --s 来控制决定边框向内还是向外移动的符号。

如果我们想要动画化原本无法动画化的属性,font-size 技巧确实很有用。使用 @property 定义的 自定义属性 可以解决此问题,但 对它的支持 在我撰写本文时仍然不足。

边框显示

我们在本系列的第一部分制作了以下显示动画

我们可以采用同样的想法,但不是使用具有纯色的边框,我们将使用这样的渐变

如果比较这两段代码,您会注意到以下更改

  1. 我在 mask 属性内使用了第一个示例中的相同渐变配置。我只是将渐变从 background 属性移动到 mask 属性。
  2. 我添加了一个 repeating-linear-gradient() 来创建渐变边框。

就是这样!我重新使用了我们已经看到的大部分相同代码——并进行了一些微小的调整——并获得了另一个带有悬停效果的酷炫图像装饰。

/* Solid color border */

img {
  --c: #8A9B0F; /* the border color */
  --b: 10px;   /* the border thickness*/
  --g: 5px;  /* the gap on hover */

  padding: calc(var(--g) + var(--b));
  --_g: #0000 25%, var(--c) 0;
  background: 
    conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))
     var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,
    conic-gradient(at bottom var(--b) left  var(--b), var(--_g))
     0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat;
  transition: .3s, background-position .3s .3s;
  cursor: pointer;
}
img:hover {
  --_i: 100%;
  transition: .3s, background-size .3s .3s;
}
/* Gradient color border */

img {
  --b: 10px; /* the border thickness*/
  --g: 5px;  /* the gap on hover */
  background: repeating-linear-gradient(135deg, #F8CA00 0 10px, #E97F02 0 20px, #BD1550 0 30px);

  padding: calc(var(--g) + var(--b));
  --_g: #0000 25%, #000 0;
  mask: 
    conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))
     var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,
    conic-gradient(at bottom var(--b) left  var(--b), var(--_g))
     0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat,
    linear-gradient(#000 0 0) content-box;
  transition: .3s, mask-position .3s .3s;
  cursor: pointer;
}
img:hover {
  --_i: 100%;
  transition: .3s, mask-size .3s .3s;
}

让我们尝试另一个边框动画。这个有点棘手,因为它有一个 三步动画

动画的第一步是使底部边缘更大。为此,我们调整 linear-gradient()background-size

您可能想知道为什么我也要添加顶部边缘。我们需要它用于第三步。我始终尝试优化我编写的代码,因此我使用一个渐变来覆盖顶部和底部两侧,但顶部渐变被隐藏并在稍后使用 mask 显示。

对于第二步,我们添加第二个渐变以显示左右边缘。但这次,我们使用 background-position 来实现。

我们可以在这里停止,因为我们已经有了两个渐变的不错效果,但我们在这里是为了突破极限,所以让我们添加一点蒙版来实现第三步。

诀窍是在显示底部和侧面之前隐藏顶部边缘,然后我们更新 mask-size(或 mask-position)以显示顶部部分。正如我之前所说,我们可以找到很多渐变配置来实现相同的效果。

这是我将要使用的渐变的插图

我正在使用两个圆锥渐变,宽度等于 200%。这两个渐变覆盖了该区域,只留下顶部部分未被覆盖(该部分稍后将不可见)。在悬停时,我滑动这两个渐变以覆盖该部分。

以下是其中一个渐变的更好插图,让您更好地了解正在发生的事情

现在我们将它放在 mask 属性内,我们就完成了!这是完整的代码

img {
  --b: 6px;  /* the border thickness*/
  --g: 10px; /* the gap */
  --c: #0E8D94;

  padding: calc(var(--b) + var(--g));
  --_l: var(--c) var(--b), #0000 0 calc(100% - var(--b)), var(--c) 0;
  background:
    linear-gradient(var(--_l)) 50%/calc(100% - var(--_i,80%)) 100% no-repeat,
    linear-gradient(90deg, var(--_l)) 50% var(--_i,-100%)/100% 200% no-repeat;  
  mask:
    conic-gradient(at 50% var(--b),#0000 25%, #000 0) calc(50% + var(--_i, 50%)) / 200%,
    conic-gradient(at 50% var(--b),#000 75%, #0000 0) calc(50% - var(--_i, 50%)) / 200%;
  transition: 
    .3s calc(.6s - var(--_t,.6s)) mask-position, 
    .3s .3s background-position,
    .3s var(--_t,.6s) background-size,
    .4s transform;
  cursor: pointer;
}
img:hover {
  --_i: 0%;
  --_t: 0s;
  transform: scale(1.2);
}

我还引入了一些变量来优化代码,但您现在应该习惯了这一点。

四步动画怎么样?是的,这是可能的!

这部分没有解释,因为这是你的作业!请运用你在本文中学习到的所有知识来分析代码,并尝试阐述它的作用。其逻辑与之前的所有示例类似。关键是隔离每个渐变,以理解动画的每个步骤。为了方便阅读,我保留了未优化的代码。如果你有兴趣,我确实有一个优化版本,但你也可以尝试自己优化代码,并将其与我的版本进行比较,以获得额外的练习。

总结

这就是关于仅使用<img>元素创建创意图像装饰的三部分系列的第二部分。我们现在对如何组合渐变和蒙版以创建令人惊叹的视觉效果,甚至动画有了很好的了解——无需使用额外的元素或伪元素。是的,一个<img>标签就足够了!

在本系列中,我们还有一篇文章要发布。在此之前,这里有一个带有酷炫悬停效果的额外演示,我使用mask来组合破碎的图像。

花哨的图像装饰系列