猪小弟的动画想必很多人小时候都看过,那长大的我们做了前端的工作,有没有用前端的想法去思考,猪小弟从圆圈里出来挥手的效果是如何实现的呢?
如果用这个效果来作为头像的效果,不仅有3D的感觉,而且仿佛头像在与人互动一样。这显然要比一张图片来的有意思,话不多说,让我们来看看实现后的效果。
看到了吗?我们将制作一个缩放动画,其中头像似乎从它所在的圆圈中弹出。很有意思吧,不要翻到页尾哦,让我们一起逐步构建这个动画效果吧!
唯一的 img
看过实现效果后,我们可能会想到用一个 div 和一个 img 元素来实现,然后 hover 和 transform: scale 来控制缩放,overflow 来控制显示范围,可是结果却不行,效果是这样的。
实际情况呢,我们仅需一个 <img src="..." /> 元素就可以实现。什么?你不信?
那就让我们用这唯一的 img 元素来实现一下,从中你将学到 CSS 渐变、遮罩、outline 属性的行为、转换以及其他许多内容。
实现
我们先从 transform 开始,其中用到了 css 的变量,对 css 变量还不了解的小伙伴可以点击链接去看一下,这里就不详细赘述了。
img {
/* 头像的大小 */
--s: 280px;
/* 边框的宽度 */
--b:5px;
width: var(--s);
height: var(--s);
cursor: pointer;
outline: var(--b) solid;
transition: 0.5s;
}
img:hover {
transform: scale(1.35);
}
边框与背景
这很简单,接下来实现图片圆形的边框与背景,这个边框我们用什么实现呢?有人说边框,直接用 border 不就行了,
可是 border 的话就会随着图片的大小一起被缩放。所以这里我们可以使用径向渐变,因为径向渐变我们可以单独控制,而且渐变之间可以硬停止,我们可以用它来绘制背景和边框,这是个完美的解决方案。
但是在写径向渐变之前我们要了解渐变尺寸的几个关键字
从上图中我们可以看出,效果一与效果二才是我们想要的,而径向渐变的默认值是效果四的样子,所以在我们实现渐变时,要使用的是效果一的关键字。
img {
/* 定义背景色的变量 */
--bgColor: #ecd078;
/* 定义边框颜色的变量 */
--borderColor: #c02942;
/* circle 定义渐变为圆形,closest-side 让渐变以元素的最短边为半径 */
/* 然后设置渐变的颜色范围( 99% 是为了解决渐变的锯齿问题 ),no-repeat center 让渐变不重复且居中 */
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
lightblue 100%) no-repeat center;
/* etc... */
}
下一步我们要做的是在悬停时调整渐变大小,随着头像的放大,渐变需要保持其大小。由于我们正在使用的 scale 是放大,所以我们实际上需要减小渐变的大小,否则它会随着头像而放大。因此,当头像放大时,我们需要渐变来缩小。也就是说我们头像放大几倍,渐变就要缩小几倍,那么就可以使用 100% / 放大的倍数 来得到渐变的大小。
img {
/* 定义一个比例值 */
--f: 1;
/* 我们在这里控制头像的放大就可以 */
transform: scale(var(--f));
/* calc(100% / var(--f)):横向保持不变 */
/* 100%:纵向跟随头像变化 */
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
lightblue 100%) no-repeat center / calc(100% / var(--f)) 100%;
/* etc... */
}
img:hover {
/* 在鼠标移入时改变比例值的大小 */
--f: 1.35;
}
我们又成功的完成了一步,但我们仍然需要隐藏头像的身体部分,这是最麻烦的部分,也是我们接下来要做的事情。
底部边框
因为渐变是一个背景,所以图片是覆盖在渐变之上的,我们要让红色的边框压在图片上边,我们就要使用一个新的东西来压一下,使用 border 的话显示是不可以的,因为他会随着头像的放大而放大,而 outline 可以做到 border 做不到的事情。
在实现之前我们先来认识了解一下 outline 中的一个关键属性,outline-offser,属性用于设置 outline 与一个元素边缘或边框之间的间隙,效果如下。
我们需要 outline 覆盖在边框之上,而默认值是紧贴着,所以我们在使用时要使用负值将 outline 向内收缩边框的宽度就可以了。
img {
/* 将 outline 的颜色改变为边框的颜色 */
outline: var(--b) solid var(--borderColor);
/* 将 outline 向内缩小距离,重合在边框之上 */
outline-offset: calc(0px - var(--b));
/* 上边的两个角不变,下边的两个角设置数值大点变为圆角,使轮廓与渐变的弯曲程度相匹配 */
border-radius: 0 0 500px 500px;
/* etc... */
}
我们再来思考一下如何让头像缩放时 outline 保持不变永远覆盖在边框之上呢?我们画个图看一下。
由上图所示我们得到了我们想要的值,那么我们将结果套入代码之中。
img {
/* 将得到的结果套入代码之中,相应的值换成变量 */
outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
/* etc... */
}
现在已经看起来越来越有意思了,我们接下来就是要去除挡在头发的线,这个很简单,让我们在顶部添加带有填充的空间,以帮助避免顶部的重叠。
img {
/* 向顶部添加带有填充的空间 */
padding-top: calc(var(--s) / 5);
/* etc... */
}
得到效果如上图所示,这是因为背景图默认填充的是 padding 的范围,我们只需要更改背景图的填充范围即可。
img {
/* 添加 content-box 属性,让背景图在内容盒中 */
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
lightblue 100%) content-box no-repeat center / calc(100% / var(--f)) 100%;
/* etc... */
}
到这里我们基本实现了效果,如果忽视掉圆圈外的干扰我们就已经完成了,所以接下来我们的最后一步就是框一个范围,让范围内的内容呈现范围外的隐藏,这时候就要依靠我们的压轴属性了 mask。
mask
由上图我们可以看出黄色框住的部分是我们要显示的部分,黄色框由一个矩形和一个圆形组成,并且我们发现一下两点。
- 圆形部分与我们的渐变背景大小完全一致,曲率相同,控制缩放自然也是相同的。
- 矩形部分覆盖的是
outline之内的区域,也就是说矩形的宽度是outline减去边框的宽度。
那我们来使用 mask 实现这个两个区域。
img {
/* 因为圆形部分与我们渐变背景大小一致 */
/* 所以圆形部分的 mask 几乎与渐变背景的样式相同,我们提取一下公告部分,定义一个变量 --_g */
--_g: content-box no-repeat center / calc(100% / var(--f)) 100%;
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
lightblue 100%) var(--_g);
-webkit-mask: radial-gradient(circle closest-side, #000 99%, #0000) var(--_g);
/* etc... */
}
接下来,还有一个矩形部分,linear-gradient。
img {
--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
outline-offset: var(--_o);
-webkit-mask: linear-gradient(#000 0 0) no-repeat 50% calc(1px - var(--_o)) / calc(100% / var(--f) - 2 * var(--b) - 2px) 50%,
radial-gradient(circle closest-side, #000 99%, #0000) var(--_g);
}
矩形部分的代码可能不太容易理解,我们暂时将 img 中的 background 与 -webkit-mask 注释掉,给 img 添加一个纯色的背景让他不重复且居中。
img {
/* background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
#0000 100%) var(--_g);
-webkit-mask: linear-gradient(#000 0 0) no-repeat 50% calc(1px - var(--_o)) / calc(100% / var(--f) - 2 * var(--b) - 2px) 50%,
radial-gradient(circle closest-side, #000 99%, #0000) var(--_g); */
background: linear-gradient(#000 0 0) no-repeat center;
}
我们就得到了这样的一个效果,可以看到背景上移了,而 outline 并没有动,回顾一下我们控制 outline 的逻辑,outline 的收缩大小正好是我们背景上移的大小,只不过 outline 收缩是负值,背景向下移动需要正值,我们来写一下。
img {
/* 因为 outline 的收缩与背景的下移有相同的代码,所以我们创建一个新的变量 --_o */
--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
outline-offset: var(--_o);
/* calc(0px - var(--_o)) 取 outline 的正值*/
background: linear-gradient(#000 0 0) no-repeat center calc(0px - var(--_o));
}
好了,现在我们的背景图位置正确了,让我们来处理一下他的大小问题。
img {
/* calc(100% / var(--f)) 因为横向的尺寸放大了,所以我们让他除以放大的比例 */
/* 50% 纵向的尺寸在整个过程中没有变化,我们让他一直保持就行 */
background: linear-gradient(#000 0 0) no-repeat center calc(0px - var(--_o)) / calc(100% / var(--f)) 50%;
}
可以看到现在的效果已经很好了,那么现在我们将 background 的代码当作矩形的代码合并到 -webkit-mask 中,将原先注释的代码还原,这里不要忘了,我们考虑过矩形的宽度是 outline 减去两边边框的宽度,要一并写入其中。并将背景色改为透明色。
img {
/* #0000 表示纯黑色的透明色 */
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
#0000 100%) var(--_g);
/* calc(100% / var(--f) - 2 * var(--b)) 在宽度的控制中减去两倍的边框宽度 */
-webkit-mask: linear-gradient(#000 0 0) no-repeat 50% calc(1px - var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%,
radial-gradient(circle closest-side, #000 99%, #0000) var(--_g);
}
到这里我们已经完全完成了,但是效果并不完美还有一个小小的 bug 存在,让我们看一下现在的效果,找找 bug 在哪。
眼神好的同学可能已经发现了,在头像缩放时会有很细的线闪动,这是因为我们在绘制 mask 矩形时完全是贴着 outline 的内边的,这就导致在缩放时由于绘制的问题导致不能完美的实现,所以我们可以在矩形减去边框时多减去一点点。
img {
/* calc(100% / var(--f) - 2 * var(--b) - 2px) 多减去 2px */
-webkit-mask: linear-gradient(#000 0 0) no-repeat 50% calc(1px - var(--_o)) / calc(100% / var(--f) - 2 * var(--b) - 2px) 50%,
radial-gradient(circle closest-side, #000 99%, #0000) var(--_g);
}
我们再来看看最终的效果。
总结
一个小小的头像效果考察到了我们很多 css 的知识, 我们使用一个 img 元素和一些 css 的技巧以及数学公式来完成了这个头像的花式悬停效果,很多小细节都在其中,我们今天探究这个头像效果实现,其实也是检验我们基本功的过程。有句古话说:合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。更高的薪资建立在更牢固的基础之上,让我们温故而知新,努力学习吧!
下面是全部的代码和原始图片,喜欢的小伙伴可以跟着上面的过程自己写一遍。
img {
--s: 280px;
--b: 5px;
--bgColor: #ecd078;
--borderColor: #c02942;
--f: 1;
--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;
--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
width: var(--s);
height: var(--s);
cursor: pointer;
padding-top: calc(var(--s) / 5);
outline: var(--b) solid var(--borderColor);
outline-offset: var(--_o);
border-radius: 0 0 500px 500px;
transition: 0.5s;
transform: scale(var(--f));
background:
radial-gradient(circle closest-side,
var(--bgColor) calc(99% - var(--b)),
var(--borderColor) calc(100% - var(--b)),
var(--borderColor) 99%,
#0000 100%) var(--_g);
-webkit-mask: linear-gradient(#000 0 0) no-repeat 50% calc(1px - var(--_o))
radial-gradient(circle closest-side, #000 99%, #0000) var(--_g);
}
img:hover {
--f: 1.35;
}
备注:学习来源 渡一Web前端学习频道