本文翻译自 A Fancy Hover Effect For Your Avatar,略有删改,有兴趣可以看看原文。
你知道当一个人的头像从一个圆圈或洞里伸出来时的那种效果吗?本文将使用一种很简洁的方式实现该悬停效果,可以用在你的头像交互上面。
如封面图所示,我们将制作一个缩放动画,其中头像部分似乎从它所在的圆圈中钻出来了。是不是很酷呢?接下来让我们一起一步一步地构建这个动画交互效果。
HTML:只需要一个元素
是的,只需要一个img
图片标签即可,本次练习的挑战性部分是使用尽可能少的代码。如果你已经关注我一段时间了,你应该习惯了。我努力寻找能够用最小、最易维护的代码实现的CSS解决方案。
<img src="" alt="">
首先我们需要一个带有透明背景的正方形图像文件,以下是本次案例使用的图像。
在开始CSS之前,让我们先分析一下效果。悬停时图像会变大,所以我们肯定会在这里使用transform:scale
。头像后面有一个圆圈,径向渐变应该可以达到这个效果。最后我们需要一种在圆圈底部创建边框的方法,该边框将不受整体放大的影响且是在视觉顶层。
放大效果
放大的效果,增加transform:scale
,这个比较简单。
img:hover { transform: scale(1.35); }
上面说过背景是一个径向渐变。我们创建一个径向渐变,但是两个颜色之间不要有过渡效果,这样使得它看起来像我们画了一个有实线边框的圆。
img { --b: 5px; /* border width */ background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), #C02942 calc(100% - var(--b)) 99%, #0000 ); }
注意CSS变量,--b
,在这里它表示“边框”的宽度,实际上只是用于定义径向渐变红色部分的位置。
下一步是在悬停时调整渐变大小,随着图像的放大,圆需要保持大小不变。由于我们正在应用scale
变换,因此实际上需要减小圆圈的大小,否则它会随着化身的大小而增大。
让我们首先定义一个CSS变量--f
,它定义了“比例因子”,并使用它来设置圆的大小。我使用1作为默认值,因为这是图像和圆的初始比例,我们从圆转换。
现在我们必须将背景定位在圆的中心,并确保它占据整个高度。我喜欢把所有东西都直接简写在 background 属性,代码如下:
background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;
背景放置在中心( 50%),宽度等于calc(100%/var(--f)),高度等于100%。
当 --f 等于 1 时是我们最初的比例。同时,渐变占据容器的整个宽度。当我们增加 --f,元素的大小会增长但是渐变的大小将减小。
越来越接近了!我们在顶部添加了溢出效果,但我们仍然需要隐藏图像的底部,这样它看起来就像是跳出了圆圈,而不是整体浮在圆圈前面。这是整个过程中比较复杂的部分,也是我们接下来要做的。
下边框
第一次尝试使用border-bottom
属性,但无法找到一种方法来匹配边框的大小与圆的大小。如图所示,相信你能看出来无法实现我们想要的效果:
实际的解决方案是使用outline
属性。不是border
。outline
可以让我们创造出很酷的悬停效果。结合 outline-offset
偏移量,我们就可以实现所需要的效果。
其核心是在图像上设置一个outline
轮廓并调整其偏移量以创建下边框。偏移量将取决于比例因子,与渐变大小相同。outline-offset
偏移量看起来相对比较复杂,这里对计算方式进行了精简,有兴趣的可以看看原文。
img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ border-radius: 0 0 999px 999px; outline: var(--b) solid var(--c); outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b)); }
因为我们需要一个圆形的底部边框,所以在底部添加了一个边框圆角,使轮廓与渐变的弯曲程度相匹配。
现在我们需要找到如何从轮廓中删除顶部,也就是上图中挡住头像的那根线。换句话说,我们只需要图像的底部轮廓。首先,在顶部添加空白和填充,以帮助避免顶部头像的重叠,这通过增加padding
即可实现:
padding-top: calc(var(--s)/5)
这里还有一个注意点,需要添加 content-box
值添加到 background
:
background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000 ) 50%/calc(100%/var(--f)) 100% no-repeat content-box;
这样做是因为我们添加了padding填充,并且我们只希望将背景设置为内容框,因此我们必须显式地定义出来。