发布于 2026-01-06 8 阅读
0

鼠标悬停时会发出闪亮效果,并跟随鼠标移动(CSS)✨

鼠标悬停时会发出闪亮效果,并跟随鼠标移动(CSS)✨

当设计师不在场时,开发者能做的最有趣的事情大概就是设计悬停状态了。你现在应该已经了解了一些基本效果:淡入、放大缩小、颜色变化、动画彩虹渐变等等。

但最近有一部动画给了我很大的启发(感谢 Keyframers 的推荐!)

这并非那种始终不变的“静态”悬停状态。它实际上会追踪你的鼠标移动,让页面更具互动性。这个想法太棒了……所以我们把它应用到了Hack4Impact网站上 😁

那么,让我们来探索一下吧。

  • 🎈 为什么 CSS 变量可以帮助我们
  • ✨ 我们如何设计按钮
  • 🪤 我们如何将鼠标移动映射到金属光泽
  • 🔨 如何将此动画适配到任何 UI 框架

前进!

我们的最终目标

hack4impact.org 网站上按钮悬停时发光的演示

表面上看效果很简单。只需在鼠标悬停在按钮上时稍微改变一下颜色,再加上一个圆形渐变来营造“金属”光泽即可。

但有些功能是 CSS 本身无法实现的:我们需要追踪光标位置才能实现交互!幸运的是,这些年来这已经变得容易多了;你甚至不需要 UI 框架或状态管理就能做到 👀

🎈 CSS变量简介

如果你还没听说过,CSS 变量现在可是席卷了整个 Web 开发领域。它们有点像SASSLESS$等预处理器提供的变量,但有一个巨大的优势:你可以用 JavaScript 在运行时修改这些变量的值😱

我们来看一个简单的例子。假设我们要做一个气球充气泵,你只需尽可能快地按下按钮就能给一个 HTML 风格的气球“充气”。

如果我们对 CSS 变量一无所知,我们很可能会直接用 JavaScript 来操作样式。以下是如何使用 CSStransform属性给气球充气:

const balloon = document.querySelector('.balloon');
// make the balloon bigger by 50%
balloon.style.transform = 'scale(1.5)';
Enter fullscreen mode Exit fullscreen mode

或者,每次点击按钮时,让气球稍微变大一点:

...
const pump = document.querySelector('.pump')
// keep track of the balloon's size in a JS variable
let size = 1;
pump.addEventListener('click', () => {
  size += 0.1;
    balloon.style.transform = `scale(${size})`;
})
Enter fullscreen mode Exit fullscreen mode

目前为止一切正常。但它也存在一些成长的烦恼:

  1. 我们需要使用一个 JS 变量来跟踪一个 CSS 属性(气球的scale大小)随着我们在应用程序中添加更多动画元素,这最终可能会演 变成一系列状态变量。
  2. 我们正在使用字符串编写 CSS。就我个人而言,这让我感觉很不好,因为我们失去了所有的语法高亮和编辑器建议。当我们想size在样式的其他部分使用该变量时,维护起来也会很麻烦。例如,如果我们想background-position随着气球的膨胀而改变它的值怎么办?或者改变它的值heightwidth或者改变某些linear-gradient具有多个颜色位置的元素呢?

CSS变量来帮忙了

正如你可能已经猜到的那样,我们可以将它size从代码中存储为一个 CSS 变量!

我们可以使用与之前相同的.style属性,这次使用setProperty函数来赋值:

let size = 1;
pump.addEventListener('click', () => {
  size += 0.1;
    balloon.style.setProperty('--size', size);
})
Enter fullscreen mode Exit fullscreen mode

transform然后,将该变量从 CSS 属性中传递进去:

.balloon {
  /* set a default / starting value if JS doesn't supply anything */
  --size: 1;
  ...
  /* use var(...) to apply the value */
  transform: scale(var(--size));
}
Enter fullscreen mode Exit fullscreen mode

你完全可以抛弃那个size变量,让 CSS 成为唯一的数据源!每次尝试递增它时,直接读取 CSS 中的值即可:

pump.addEventListener('click', () => {
  // Note: you *can't* use balloon.style here!
  // This won't give you the up-to-date value of your variable.
  // For that, you'll need getComputedStyle(...)
    const size = getComputedStyle(balloon).getPropertyValue('--size');
  // size is a string at this stage, so we'll need to cast it to a number
  balloon.style.setProperty('--size', parseFloat(size) + 0.1)
})
Enter fullscreen mode Exit fullscreen mode

当然,这其中也有一些需要注意的地方。例如,CSS 变量在检索时始终是字符串,因此你需要根据需要将其转换为 `int`int或 `int` (用于表示小数)。此外,`int` 和 `int`之间的区别也比较难记,所以选择对你来说最合适的方式即可!float.stylegetComputedStyle

这里有一个完整的示例,可以增强你的信心🎈

✨ 让我们开始启动我们闪亮的按钮吧

在检验我们新掌握的 CSS 变量知识之前,让我们先来看看这个按钮需要哪些样式。

记住,我们希望鼠标光标移动时,颜色能够平滑渐变,就像光线照射在金属上一样。你可以想象,我们需要一个可以轻松移动的渐变区域radial-gradientbutton

我们可以给按钮添加渐变作为辅助背景(没错,你可以在同一个元素上叠加多个背景!)。但为了简单起见,我们只需在按钮内部添加另一个元素来表示“闪亮”效果。为了美观,我们将使用伪元素来实现这一点😁

.shiny-button {
  /* add this property to our button, */
  /* so we can position our shiny gradient *relative* to the button itself */
  position: relative;
  /* then, make sure our shiny effect */
  /* doesn't "overflow" outside of our button */
  overflow: hidden;
  background: #3984ff; /* blue */
  ...
}

.shiny-button::after {
  /* all pseudo-elements need "content" to work. We'll make it empty here */
  content: '';
  position: absolute;
  width: 40px;
  height: 40px;
  /* make sure the gradient isn't too bright */
    opacity: 0.6;
  /* add a circular gradient that fades out on the edges */
    background: radial-gradient(white, #3984ff00 80%);
}
Enter fullscreen mode Exit fullscreen mode

附注:您可能已经注意到渐变背景上的 8 位十六进制代码。这是一个很棒的功能,可以让您为十六进制代码添加透明度!点击此处了解更多信息。

太好了!这样一来,我们应该就能看到按钮上呈现出一种柔和的、固定的渐变效果了。

🪤 现在,让我们来追踪一些鼠标光标。

我们需要深入研究一些浏览器原生 API。你可能click99% 的时间都在监听事件,所以很容易忘记我们还可以调用其他几十个事件监听器!我们需要使用以下mousemove事件来实现我们的目的:

const button = document.querySelector('.shiny-button')
button.addEventListener('mousemove', (e) => {
    ...
})
Enter fullscreen mode Exit fullscreen mode

如果我们注销或event提出异议,这里会显示一些有用的值。我们主要关注的是 `<mouse_position>`clientX和 `<mouse_position> clientY`,它们分别表示鼠标相对于整个屏幕的位置。将鼠标悬停在此按钮上即可查看这些值:

这很有用,但并非我们想要的信息。请记住,我们的闪光效果是对于周围的按钮定位的。例如,要将效果定位在按钮的左上角,我们需要设置 `$($($($($($($($($($($($($($($($($( $( $( $($( $ top: 0; left: 0;( $ ( $ ' x: 0 y: 0...clientXclientY

没有现成的event属性可以做到这一点,所以我们需要稍微动动脑筋。记住这一点,clientXclientY告诉我们光标相对于当前窗口的位置。还有一个很棒的函数叫做 ` keypoint()`,它可以获取按钮getBoundingClientRect()相对于窗口的 x 和 y 坐标。所以,如果我们用光标的位置减去按钮的位置……就能得到光标相对于按钮的位置了!

用可视化的方式可能更容易理解。将鼠标悬停在上面,看看我们的mouse值、boundingClientRect值和减去的值是如何相互作用的:

💅 将这些坐标传递给 CSS

好了,让我们把这些信息联系起来!我们将从监听器传递值mousemove

button.addEventListener("mousemove", (e) => {
  const { x, y } = button.getBoundingClientRect();
  button.style.setProperty("--x", e.clientX - x);
  button.style.setProperty("--y", e.clientY - y);
})
Enter fullscreen mode Exit fullscreen mode

接下来,我们将向之前那个漂亮的伪元素添加一些 CSS 变量:

.shiny-button::after {
  ...
  width: 100px;
  height: 100px;
  top: calc(var(--y, 0) * 1px - 50px);
  left: calc(var(--x, 0) * 1px - 50px);
}
Enter fullscreen mode Exit fullscreen mode

这里有几点需要说明:

  1. 我们可以使用第二个参数为变量设置默认值var。在本例中,我们将两个值都设为 0。

  2. CSS 变量的“类型”概念有点奇怪。这里,我们假设会将 `a`x和 `b`y作为整数传递。这在 JavaScript 中是合理的,但 CSS 很难理解类似 `a` 这样的值10 实际上代表的是10px`b`。要解决这个问题,只需乘以你想要的单位calc(即使用 `\n` * 1px)。

  3. width我们从定位值中减去一半height。这样可以确保闪亮的圆圈与光标位于同一中心,而不是跟随左上角。

淡入我们的入口效果

我们差不多完成了!只剩最后一个小调整:如果我们保持这个动画原样,闪亮效果就会始终显示在按钮的某个角落(即使我们没有将鼠标悬停在上面)。

我们可以用 JavaScript 来显示和隐藏这个效果。但是既然 CSS 已经允许你通过鼠标悬停来设置样式,为什么还要这样做呢?

/* to explain this selector, we're */
/* selecting our ::after element when the .shiny-button is :hover-ed over */
.shiny-button:hover::after {
  /* show a faded shiny effect on hover */
  opacity: 0.4;
}
.shiny-button::after {
  ...
  opacity: 0;
  /* ease into view when "transitioning" to a non-zero opacity */
  transition: opacity 0.2s;
}
Enter fullscreen mode Exit fullscreen mode

搞定!只需添加一条线过渡效果,就能得到漂亮的淡入效果。这就是我们的最终成品✨

🔨 适应你选择的框架

我明白,你可能会觉得这篇文章没什么价值,eventListeners心想“框架 X 里的 JS 肯定跟现在大不相同”。幸运的是,过渡过程非常平滑!

首先,你需要获取要启用 Shine 功能的按钮的引用useRef。在 React 中,我们可以使用hook 来获取它:

const ShinyButton = () => {
  // null to start
  const buttonRef = React.useRef(null)
  React.useEffect(() => {
    // add a useEffect to check that our buttonRef has a value
    if (buttonRef) {
      ...
    }
  }, [buttonRef])

  return <button ref={buttonRef}>✨✨✨</button>
}
Enter fullscreen mode Exit fullscreen mode

或者在 Svelte 中,我们可以将bind元素赋值给一个变量:

<script>
  import { onMount } from 'svelte'
  let buttonRef
  // our ref always has a value onMount!
  onMount(() => {
    ...
  })
</script>

<button bind:this={buttonRef}>✨✨✨</button>
Enter fullscreen mode Exit fullscreen mode

题外话:我总是喜欢加入 Svelte 的例子,因为它们通常更容易理解😁

一旦我们有了这个参考信息,我们的房产设置就可以照常进行:

React 示例

const ShinyButton = () => {
  const buttonRef = React.useRef(null)
  // throw your mousemove callback up here to "add" and "remove" later
  // might be worth a useCallback based on the containerRef as well!
  function mouseMoveEvent(e) {
    const { x, y } = containerRef.current.getBoundingClientRect();
    containerRef.current.style.setProperty('--x', e.clientX - x);
    containerRef.current.style.setProperty('--y', e.clientY - y);
  }

  React.useEffect(() => {
    if (buttonRef) {
      buttonRef.current.addEventListener('mousemove', mouseMoveEvent)
    }
    // don't forget to *remove* the eventListener
    // when your component unmounts!
    return () => buttonRef.current.removeEventListener('mousemove', mouseMoveEvent)
  }, [buttonRef])
  ...
Enter fullscreen mode Exit fullscreen mode

Svelte 示例

<script>
  import { onMount, onDestroy } from 'svelte'
  let buttonRef
  // again, declare your mousemove callback up top
  function mouseMoveEvent(e) {
    const { x, y } = buttonRef.getBoundingClientRect();
    buttonRef.style.setProperty('--x', e.clientX - x);
    buttonRef.style.setProperty('--y', e.clientY - y);
  }
  onMount(() => {
        buttonRef.addEventListener('mousemove', mouseMoveEvent)
  })
  onDestroy(() => {
    buttonRef.removeEventListener('mousemove', mouseMoveEvent)
  })
</script>
Enter fullscreen mode Exit fullscreen mode

重点:💡组件卸载时,不要忘记移除事件监听器!

请查看我们在 Hack4Impact 上的实时示例

如果你想看看它在实际应用中的效果,可以查看我们 Hack4Impact 网站的 CodeSandbox 示例。我们还添加了一些 CSS 特效,让这个效果可以应用到任何元素上,而不仅仅是按钮 ✨

要查看该组件,请点击此处

学到点东西?

太棒了!如果你错过了,我推出了我的“网络魔法”简报,里面会分享更多类似的知识宝典!

这篇文章探讨了Web 开发的“基本原理”。换句话说,它揭示了那些让所有 Web 项目得以运行的浏览器 API、CSS 规则和半可访问的 HTML 代码背后的原理。如果你想超越框架的局限,那么这篇文章就是为你准备的,亲爱的 Web 魔法师🔮

点击这里订阅哦!我保证只发布教学内容,绝不发送垃圾信息❤️

文章来源:https://dev.to/hack4impact/a-shiny-on-hover-effect-that-follows-your-mouse-css-4d5b