在保留边界半径的同时扩展框的各种方法

我最近注意到CodePen上的一个有趣变化:将鼠标悬停在主页上时,有一个矩形,其圆角向后扩展。

动画gif,在悬停时记录CodePen扩展框效果。
CodePen主页上的扩展框效果。

作为我的好奇生物,我不得不检查它是如何工作的! 事实证明,后面的矩形是绝对位置::after伪元素。

大学。左侧是一个DevTools屏幕截图,显示了:: after伪元素上应用的初始样式。相关的是使它们绝对定位的位置,其顶部和左侧偏移为1rem,右侧和底部偏移为-1rem。在右侧,我们对这些样式进行了说明,显示了父元素框,:: after框及其边缘之间的偏移量。
初始::after样式。 正偏移量从父级的padding限制向内,而负偏移量向外。

:hover ,其偏移量被覆盖,并与transition结合使用,我们得到了扩展框效果。

大学。左侧是一个DevTools屏幕截图,显示了:: after伪元素上应用的:hover样式。所有这些偏移量都将覆盖初始偏移量,并使:: after的边界在除右侧之外的所有方向上向外偏移2rem。在右侧,我们对这些样式进行了说明,显示了父元素框,:: after框及其边缘之间的偏移量。
:hover上的::after样式。

在初始和:hover规则集中, right属性具有相同的值( -1rem ),因此没有必要覆盖它,但是所有其他偏移量向外移动2remtopleft偏移量从1rem-1rem bottom偏移从-1rem-3rem

这里要注意的一件事是::after伪元素的border-radius10px ,它在扩展时会保留下来。 这让我思考了在保留其border-radius同时我们有什么方法来扩展/缩小(伪)元素。 你能想到几个? 如果您有未包含的想法,请告诉我们,我们在其中查看一堆选项,并找出最适合哪种情况的选项。

改变偏移量

这是CodePen上使用的方法,由于多种原因,它在这种特殊情况下确实能很好地工作。 首先,它具有强大的支持。 当扩展(伪)元素具有响应性,没有固定尺寸并且同时其扩展量是固定的( rem值)时,它也可以工作。 它还可以在两个以上的方向上展开(在这种情况下为topbottomleft )。

但是,我们需要注意一些警告。

首先,我们的扩展元素不能具有position: static 。 在CodePen用例的上下文中,这不是问题,因为::after伪元素无论如何都必须绝对放置,以便放置在该父项的其余内容之下。

其次,过度使用偏移动画(以及通常使用偏移量,边距,边框宽度,填充或尺寸来影响具有框属性的布局的动画)通常会对性能产生负面影响。 再次,这不是在这里,我们只是在:hover上有一点transition ,没什么大不了的。

改变尺寸

除了更改偏移量,我们还可以更改尺寸。 但是,如果我们希望我们的(pseudo-)元素最多在两个方向上扩展,则此方法有效。 否则,我们还需要更改偏移量。 为了更好地理解这一点,让我们考虑一下CodePen的情况,我们希望::after伪元素在三个方向( topbottomleft )扩展。

相关的初始大小调整信息如下:

.single-item::after {
  top: 1rem;
  right: -1rem;
  bottom: -1rem;
  left: 1rem;
}

由于相对的偏移量( top - bottomleft - right对)彼此抵消( 1rem - 1rem = 0 ),因此导致伪元素的尺寸等于其父元素的尺寸(或父元素尺寸的100% )。

因此,我们可以将以上内容重写为:

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: 100%;
  height: 100%;
}

:hover ,我们增加了width2remleftheight4rem2remtop2rembottom 。 但是,只需编写:

.single-item::after {
  width: calc(100% + 2rem);
  height: calc(100% + 4rem);
}

…还不够,因为这会使height向下增加4rem而不是向上增加2rem和向下减少2rem 。 下面的演示说明了这一点(将:focus放在或悬停在项目上,以查看::after伪元素如何扩展):

由thebabydino( @thebabydino )上CodePen

我们还需要更新top属性,以获得所需的效果:

.single-item::after {
  top: -1rem;
  width: calc(100% + 2rem);
  height: calc(100% + 4rem);
}

哪个可行,如下所示:

由thebabydino( @thebabydino )上CodePen

但是,老实说,这感觉比单独更改偏移量要差。

但是,在其他情况下更改尺寸是一个很好的解决方案,例如当我们想要一些带有圆角的钢筋沿单个方向扩展/收缩时。

由thebabydino( @thebabydino )上CodePen

请注意, 如果没有要保留的圆角 ,更好的解决方案是通过transform属性使用方向缩放。

更改填充/边框宽度

与更改尺寸类似,我们可以更改paddingborder-width (对于transparentborder )。 请注意,就像更改尺寸一样,如果将框扩展到两个以上的尺寸,我们还需要更新偏移量:

由thebabydino( @thebabydino )上CodePen

在上面的演示中,粉红色框表示::after伪元素的content-box ,您可以看到它保持相同的大小,这对于此方法很重要。

为了理解为什么它很重要,请考虑其他限制:我们还需要使框的尺寸由两个偏移量加上widthheight来定义,而不是使用所有四个偏移量。 这是因为padding / border-width仅向内增加(如果我们使用四个偏移量而不是两个,再加上widthheight

由thebabydino( @thebabydino )上CodePen

出于相同的原因,我们不能在::after伪元素上使用box-sizing: border-box

由thebabydino( @thebabydino )上CodePen

尽管有这些限制,但如果我们扩展的(pseudo-)元素具有文本内容,而我们不想看到它在:hover上移动,则此方法会派上用场,如下面的Pen所示,其中前两个示例更改了offsets /尺寸,而最后两个更改填充/边框宽度:

由thebabydino( @thebabydino )上CodePen

变动保证金

使用此方法,我们首先将偏移量设置为:hover状态值,并设置一个margin以进行补偿并为我们提供初始状态大小:

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3rem;
  left: -1rem;
  margin: 2rem 0 2rem 2rem;
}

然后我们在:hover上将这个margin归零:

.single-item:hover::after { margin: 0 }

由thebabydino( @thebabydino )上CodePen

这是另一种适用于CodePen情况的方法,尽管我不能真正想到其他用例。 还要注意,就像更改偏移量或尺寸一样,此方法也会影响content-box的大小,因此我们可能已经移动和重新排列的所有文本内容。

更改字体大小

这可能是最棘手的问题之一,并且有很多局限性,其中最重要的是我们不能在扩展(或收缩)的实际(伪)元素上包含文本内容,但这是另一种在CodePen情况下效果很好的方法。

此外, font-size本身并不能使框扩展或收缩。 我们需要将其与前面讨论的属性之一结合起来。

例如,我们可以将::after上的font-size设置为等于1rem ,将偏移量设置为扩展大小写,并设置与扩展状态和初始状态之间的差相对应的em边距。

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3rem;
  left: -1rem;
  margin: 2em 0 2em 2em;
  font-size: 1rem;
}

然后,在:hover ,将font-size设为0

.single-item:hover::after { font-size: 0 }

由thebabydino( @thebabydino )上CodePen

我们也可以使用带有偏移量的font-size ,尽管它变得更加复杂:

.single-item::after {
  top: calc(2em - 1rem);
  right: -1rem;
  bottom: calc(2em - 3rem);
  left: calc(2em - 1rem);
  font-size: 1rem;
}

.single-item:hover::after { font-size: 0 }

尽管如此,重要的是它可以正常工作,如下所示:

由thebabydino( @thebabydino )上CodePen

font-size和尺寸的结合更加困难,因为我们还需要更改:hover的垂直偏移值:

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: calc(100% + 2em);
  height: calc(100% + 4em);
  font-size: 0;
}

.single-item:hover::after {
  top: -1rem;
  font-size: 1rem
}

哦,嗯,​​至少它能起作用:

由thebabydino( @thebabydino )上CodePen

font-sizepadding / border-width

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: 100%;
  height: 100%;
  font-size: 0;
}

.single-item:nth-child(1)::after {
  padding: 2em 0 2em 2em;
}

.single-item:nth-child(2)::after {
  border: solid 0 transparent;
  border-width: 2em 0 2em 2em;
}

.single-item:hover::after {
  top: -1rem;
  font-size: 1rem;
}

由thebabydino( @thebabydino )上CodePen

规模变化

如果您已阅读过有关animation性能的文章,那么您可能已经读过,最好对变形进行动画处理,而不是对布局产生影响的属性(例如偏移,边距,边框,填充,尺寸)进行动画处理,这几乎是我们迄今为止所使用的!

此处突出的第一个问题是缩放元素也会缩放其角圆角,如下所示:

由thebabydino( @thebabydino )上CodePen

我们还可以通过另一种方式缩放border-radius来解决此问题。

假设我们将元素沿x轴缩放$fx并沿y轴缩放$fy ,并且希望将其border-radius保持在恒定值$r

这意味着我们还需要将$r除以沿每个轴的相应缩放比例。

border-radius: #{$r/$fx}/ #{$r/$fy};
transform: scale($fx, $fy)

由thebabydino( @thebabydino )上CodePen

但是,请注意,使用此方法时,我们需要使用缩放因子,而不是在这个方向或那个方向上扩展(伪)元素的数量。 获取缩放因子从尺寸和膨胀量是可能的,但前提是他们在它们之间有一定的固定的关系单位会表示。 尽管由于1in始终为96px的事实,预处理器可以混合inpx类的单位,但由于缺少上下文,它们无法解析px多少1em1%1vmin1chcalc()也不是解决方案,因为它不允许我们将长度值除以另一个长度值以获得无单位的比例因子。

这就是为什么在CodePen情况下缩放不是解决方案的原因,其中::after框的尺寸取决于视口,并且同时以固定的rem量进行扩展。

但是,如果给出了比例大小或我们可以轻松地对其进行计算,则可以考虑采用这种方法,尤其是因为使比例因子成为自定义属性,然后用一些Houdini魔术制作动画可以大大简化我们的代码。

border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
transform: scale(var(--fx), var(--fy))

请注意,Houdini仅在启用了“ 实验性Web平台功能”标志的Chromium浏览器中起作用。

例如,我们可以创建此图块网格动画:

循环播放拼贴网格动画( 演示 ,仅带有标志的Chrome)

正方形瓷砖的边长$l ,圆角为$k*$l

.tile {
  width: $l;
  height: $l;
  border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
  transform: scale(var(--fx), var(--fy))
}

我们注册了两个自定义属性:

CSS.registerProperty({
  name: '--fx', 
  syntax: '<number>', 
  initialValue: 1, 
  inherits: false
});

CSS.registerProperty({
  name: '--fy', 
  syntax: '<number>', 
  initialValue: 1, 
  inherits: false
});

然后我们可以为它们设置动画:

.tile {
  /* same as before */
  animation: a $t infinite ease-in alternate;
  animation-name: fx, fy;
}

@keyframes fx {
  0%, 35% { --fx: 1 }
  50%, 100% { --fx: #{2*$k} }
}

@keyframes fy {
  0%, 35% { --fy: 1 }
  50%, 100% { --fy: #{2*$k} }
}

最后,我们根据水平( --i )和垂直( --j )网格索引添加一个延迟,以创建交错的animation效果:

animation-delay: 
  calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{$t}), 
  calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{1.5*$t})

另一个示例是以下示例,其中点是在伪元素的帮助下创建的:

循环播放尖峰动画( 演示 ,仅带标志的Chrome)

由于伪元素与它们的父元素一起缩放,因此我们还需要反转对其的缩放变换:

.spike {
  /* other spike styles */
  transform: var(--position) scalex(var(--fx));

  &::before, &::after {
    /* other pseudo styles */
    transform: scalex(calc(1/var(--fx)));
  }
}

正在更改…剪切路径?!

我确实很喜欢这种方法,即使它取消了Chromium Edge之前的版本和Internet Explorer的支持。

几乎所有clip-path用法示例都具有polygon()值或SVG参考值。 但是,如果您看过我以前的文章,那么您可能知道我们可以使用其他基本形状,例如inset() ,其工作原理如下图所示:

该图显示了inset()函数的四个值代表什么。第一个是裁剪矩形的上边缘相对于边框的上边缘的偏移。第二个是剪裁矩形的右边缘相对于边框的右边缘的偏移。第三个是裁剪矩形的底边相对于边框的底边的偏移。第四个是剪裁矩形的左边缘相对于边框的左边缘的偏移量。
inset()函数的工作方式。 ( 演示

因此,为了使用此方法重现CodePen效果,我们将::after偏移量设置为扩展状态值,然后在clip-path的帮助下切出我们不希望看到的内容:

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3em;
  left: -1em;
  clip-path: inset(2rem 0 2rem 2rem)
}

然后,在:hover状态,我们将所有插图归零:

.single-item:hover::after {
  clip-path: inset(0)
}

可以在下面的操作中看到:

由thebabydino( @thebabydino )上CodePen

好的,这可行,但是我们还需要一个圆角。 幸运的是, inset()使我们可以将其指定为我们希望的任何border-radius值。

在这里,沿两个方向的所有角都设置一个10px

.single-item::after {
  /* same styles as before */
  clip-path: inset(2rem 0 2rem 2rem round 10px)
}

.single-item:hover::after {
  clip-path: inset(0 round 10px)
}

这正是我们所要追求的目标:

由thebabydino( @thebabydino )上CodePen

此外,它在不支持的浏览器中并没有真正破坏任何东西,只是始终处于扩展状态。

但是,尽管此方法在很多情况下都适用,包括CodePen用例,但当我们的扩展/收缩元素的后代超出其剪切父对象的border-box ,该方法将不起作用,例如最后一个示例是前面讨论过的缩放方法。

翻译自: https://css-tricks.com/various-methods-for-expanding-a-box-while-preserving-the-border-radius/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值