如此丝滑的按钮交互效果

25,635 阅读6分钟

今天分享一个很有特色的按钮交互效果,如封面图所示,保证让你停不下来,原作者是Adam Kuhn,有兴趣的可以去codepen体验,地址:codepen,本文将核心功能逐一讲解。

基于这个动图可以将主要实现的几个功能点拆分为以下几点:

  • 按钮的径向渐变背景色可以随着鼠标的移动变化
  • 按钮的背景区域会随着鼠标的移动产生弹性变化效果
  • 按钮的文字阴影会随着鼠标的变化而变化

鼠标位置获取

在正式开始前做一些准备工作,分析主要的这几个功能点可以发现每个功能都和鼠标的移动有关,都需要借助于鼠标移动的坐标,所以我们首先获取鼠标的位置并传递到css中,代码如下:

document.querySelectorAll(".inner").forEach((button) => {
  button.onmousemove = (e) => {
    const target = e.target;
    const rect = target.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    button.style.setProperty("--x", `${x}px`);
    button.style.setProperty("--y", `${y}px`);
    button.style.setProperty("--height", `${rect.height}px`);
    button.style.setProperty("--width", `${rect.width}px`);
  };
});

这里除开传递鼠标的位置,还传递了当前按钮的宽高用于后续按钮文案阴影的依赖。

径向渐变背景动起来

背景色默认是纯色,随着鼠标的产生变化,所以这里和两个关键点有关,鼠标移入hover,移动过程中的坐标变化。实现过程核心是通过background定义两个背景色,默认的显示部分background-size是100%,渐变部分的background-size是0,待hover时设置为100%,这时就会显示渐变背景色内容了。

  background: 
    // 渐变背景色
    radial-gradient(
      circle at center,
      var(--lightest),
      var(--light) 5%,
      var(--dark) 30%,
      var(--darkest) 50%
    ),
    // 默认显示背景色
    var(--darkest);
  background-size: 0px 0px, 100%;

  :hover {
    background-size: 100%, 100%;
  }

显示之后要动起来,基于js传入的坐标值应用到transformtranslate平移,这里注意移动是要基于当前元素的中心点位所以x和y都要减去自身的50%。

transform: translate(calc(var(--x) - 50%), calc(var(--y) - 50%));

如图所示,绿色区域是按钮部分,整个背景的中心点要和鼠标移动的坐标一致,所以要减去自身宽高的各一半。还有一点需要注意的是不能在移动的过程中让背景色漏出,所以背景区域是整个按钮的2倍。

这时整个背景区域很大,这里使用了CSS3的混合模式mix-blend-mode: lighten,最终只会应用亮色部分也就是中间的绿色区域。这里的混合模式给下一步中的弹性伸缩效果起到重要的作用。

此时的效果就是这样的,原代码在此基础上还增加了transition和filter体验让效果更佳,因涉及篇幅较长这里就不一一说明了,

背景区域弹性变化交互效果

背景弹性交互效果需要增加一个元素,与当前按钮同级别。此时的html如下:

<div class="inner">
  <button type="button">南城FE</button>
  <div class="blob"></div>
</div>

blob元素和button都使用了绝对定位,因为按钮上面有文字,所以层级上button更高。blob元素增加了两个伪元素,先看after

&:after {
  width: calc(100% - 4rem);
  height: calc(100% - 4rem);
  top: 2rem;
  left: 2rem;
  border-radius: 5rem;
  box-shadow: 0 0 0 8rem #fff;
}

基于当前界面减少实际按钮的区域,并通过定位居中,再通过box-shadow填充白色背景,还增加了圆角,此时按钮的背景变成如下所示,按钮的雏形已经有了。

然后before主要也是通过box-shadow来增加额外的元素显示,分为三个部分,中间部分跟随鼠标移动,上下两个部分为鼠标移动到边界的反向效果区域。核心代码如下:

box-shadow: 0 0 0 0.75rem #fff, 0 -8rem 0 2rem #fff, 0 8rem 0 2rem #fff;

再配合基于js传入的坐标值应用到translate平移,box-shadow部分的内容即可跟随鼠标动起来了。这里用到了一个css3的函数clamp,它可以用来限制一个值的范围。clamp函数接受三个参数,分别表示最小值、推荐值和最大值。函数的返回值为推荐值,但是它会被限制在最小值和最大值之间。所以这里超出按钮的显示区域会有临界点,不会完全脱离,核心代码如下:

transform: translate(
  clamp(5%, calc(var(--x) - 50%), 550%),
  clamp(1rem, calc(var(--y) - 50%), 5rem)
);

此时按钮的效果如下,圆形部分即是上面的0 0 0 0.75rem #fff,下面的半圆即是0 8rem 0 2rem #fff,因为增加了圆角border-radius: 100%所以都是圆形。为什么下面的是半圆白色,因为after中的box-shadow白色背景遮挡了,所以不会完全显示,又因为是白色阴影加上混合模式所以这块区域以亮色白色显示。

是不是和目标效果有些接近了,加上一行关键代码即可。

filter: blur(12px) contrast(50);

这里使用filter属性处理,首先对元素进行模糊处理,如果只是增加模糊的效果如下,可以看到增加的伪元素圆形都被磨平了,完美的融入到了按钮本身的背景色中。

再加上contrast调整元素的对比度即可达到最终的效果,这里切记执行的顺序不能写反。在CSS中 filter 属性中的函数是按照从左到右的顺序执行的。如果你在 filter 属性中使用了多个函数,那么它们会按照从左到右的顺序依次执行。

按钮的文字阴影变化

文字的阴影变化主要是改变其水平和垂直的偏移量,以及模糊半径,这里就要用到最开始传入的按钮宽高的数据了,因为偏移量的计算会基于整个按钮的面积,这样才会显得更逼真。

先看水平和垂直的偏移量,核心还是基于clamp函数,设置最小值,最大值,中间的推荐值则会随着鼠标的坐标值变化而变化,具体的数值有兴趣的可以调整体验,以下是文字阴影的水平和垂直的偏移量计算的代码:

clamp(-6px, calc((var(--width) / 2 - var(--x)) / 12), 6px)
clamp(-4px, calc((var(--height) / 2 - var(--y)) / 16), 4px)

然后是模糊半径的计算,这里用到了max函数,最大取5px,其他情况基于坐标值和宽高计算得出。

max(
  calc((var(--width) / 2 - var(--x)) / 8 +
      ((var(--height) / 2 - var(--y)) / 3)),
  calc((
        ((var(--width) / 2 - var(--x)) / 8) +
        ((var(--height) / 2 - var(--y)) / 3)
      ) * -1
  ),
  5px
)

最终的效果如下:

最后

到此整个核心的实现过程就结束了,整个代码中我们使用了box-shadowtext-shadowmix-blend-modefilter等属性,还有CSS3函数maxclampcalc。还有transition动画相关没有说明,涉及的知识点比较多,有兴趣的同学可以看源码了解。

在线代码预览:

到此本文就结束了,看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

本文正在参加「金石计划」