鼠标悬停时会发出闪亮效果,并跟随鼠标移动(CSS)✨
当设计师不在场时,开发者能做的最有趣的事情大概就是设计悬停状态了。你现在应该已经了解了一些基本效果:淡入、放大缩小、颜色变化、动画彩虹渐变等等。
但最近有一部动画给了我很大的启发(感谢 Keyframers 的推荐!)
这并非那种始终不变的“静态”悬停状态。它实际上会追踪你的鼠标移动,让页面更具互动性。这个想法太棒了……所以我们把它应用到了Hack4Impact网站上 😁
那么,让我们来探索一下吧。
- 🎈 为什么 CSS 变量可以帮助我们
- ✨ 我们如何设计按钮
- 🪤 我们如何将鼠标移动映射到金属光泽
- 🔨 如何将此动画适配到任何 UI 框架
前进!
我们的最终目标
表面上看效果很简单。只需在鼠标悬停在按钮上时稍微改变一下颜色,再加上一个圆形渐变来营造“金属”光泽即可。
但有些功能是 CSS 本身无法实现的:我们需要追踪光标位置才能实现交互!幸运的是,这些年来这已经变得容易多了;你甚至不需要 UI 框架或状态管理就能做到 👀
🎈 CSS变量简介
如果你还没听说过,CSS 变量现在可是席卷了整个 Web 开发领域。它们有点像SASS和LESS$等预处理器提供的变量,但有一个巨大的优势:你可以用 JavaScript 在运行时修改这些变量的值😱
我们来看一个简单的例子。假设我们要做一个气球充气泵,你只需尽可能快地按下按钮就能给一个 HTML 风格的气球“充气”。
如果我们对 CSS 变量一无所知,我们很可能会直接用 JavaScript 来操作样式。以下是如何使用 CSStransform属性给气球充气:
const balloon = document.querySelector('.balloon');
// make the balloon bigger by 50%
balloon.style.transform = 'scale(1.5)';
或者,每次点击按钮时,让气球稍微变大一点:
...
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})`;
})
目前为止一切正常。但它也存在一些成长的烦恼:
- 我们需要使用一个 JS 变量来跟踪一个 CSS 属性(气球的
scale大小)。随着我们在应用程序中添加更多动画元素,这最终可能会演 变成一系列状态变量。 - 我们正在使用字符串编写 CSS。就我个人而言,这让我感觉很不好,因为我们失去了所有的语法高亮和编辑器建议。当我们想
size在样式的其他部分使用该变量时,维护起来也会很麻烦。例如,如果我们想background-position随着气球的膨胀而改变它的值怎么办?或者改变它的值height?width或者改变某些linear-gradient具有多个颜色位置的元素呢?
CSS变量来帮忙了
正如你可能已经猜到的那样,我们可以将它size从代码中存储为一个 CSS 变量!
我们可以使用与之前相同的.style属性,这次使用setProperty函数来赋值:
let size = 1;
pump.addEventListener('click', () => {
size += 0.1;
balloon.style.setProperty('--size', size);
})
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));
}
你完全可以抛弃那个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)
})
当然,这其中也有一些需要注意的地方。例如,CSS 变量在检索时始终是字符串,因此你需要根据需要将其转换为 `int`int或 `int` (用于表示小数)。此外,`int` 和 `int`之间的区别也比较难记,所以选择对你来说最合适的方式即可!float.stylegetComputedStyle
这里有一个完整的示例,可以增强你的信心🎈
✨ 让我们开始启动我们闪亮的按钮吧
在检验我们新掌握的 CSS 变量知识之前,让我们先来看看这个按钮需要哪些样式。
记住,我们希望鼠标光标移动时,颜色能够平滑渐变,就像光线照射在金属上一样。你可以想象,我们需要一个可以轻松移动的渐变区域radial-gradient。button
我们可以给按钮添加渐变作为辅助背景(没错,你可以在同一个元素上叠加多个背景!)。但为了简单起见,我们只需在按钮内部添加另一个元素来表示“闪亮”效果。为了美观,我们将使用伪元素来实现这一点😁
.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%);
}
附注:您可能已经注意到渐变背景上的 8 位十六进制代码。这是一个很棒的功能,可以让您为十六进制代码添加透明度!点击此处了解更多信息。
太好了!这样一来,我们应该就能看到按钮上呈现出一种柔和的、固定的渐变效果了。
🪤 现在,让我们来追踪一些鼠标光标。
我们需要深入研究一些浏览器原生 API。你可能click99% 的时间都在监听事件,所以很容易忘记我们还可以调用其他几十个事件监听器!我们需要使用以下mousemove事件来实现我们的目的:
const button = document.querySelector('.shiny-button')
button.addEventListener('mousemove', (e) => {
...
})
如果我们注销或event提出异议,这里会显示一些有用的值。我们主要关注的是 `<mouse_position>`clientX和 `<mouse_position> clientY`,它们分别表示鼠标相对于整个屏幕的位置。将鼠标悬停在此按钮上即可查看这些值:
这很有用,但并非我们想要的信息。请记住,我们的闪光效果是相对于周围的按钮定位的。例如,要将效果定位在按钮的左上角,我们需要设置 `$($($($($($($($($($($($($($($($($( $( $( $($( $ top: 0; left: 0;( $ ( $ ' x: 0 y: 0...clientXclientY
没有现成的event属性可以做到这一点,所以我们需要稍微动动脑筋。记住这一点,clientX并clientY告诉我们光标相对于当前窗口的位置。还有一个很棒的函数叫做 ` 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);
})
接下来,我们将向之前那个漂亮的伪元素添加一些 CSS 变量:
.shiny-button::after {
...
width: 100px;
height: 100px;
top: calc(var(--y, 0) * 1px - 50px);
left: calc(var(--x, 0) * 1px - 50px);
}
这里有几点需要说明:
-
我们可以使用第二个参数为变量设置默认值
var。在本例中,我们将两个值都设为 0。 -
CSS 变量的“类型”概念有点奇怪。这里,我们假设会将 `a`
x和 `b`y作为整数传递。这在 JavaScript 中是合理的,但 CSS 很难理解类似 `a` 这样的值10实际上代表的是10px`b`。要解决这个问题,只需乘以你想要的单位calc(即使用 `\n`* 1px)。 -
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;
}
搞定!只需添加一条线过渡效果,就能得到漂亮的淡入效果。这就是我们的最终成品✨
🔨 适应你选择的框架
我明白,你可能会觉得这篇文章没什么价值,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>
}
或者在 Svelte 中,我们可以将bind元素赋值给一个变量:
<script>
import { onMount } from 'svelte'
let buttonRef
// our ref always has a value onMount!
onMount(() => {
...
})
</script>
<button bind:this={buttonRef}>✨✨✨</button>
题外话:我总是喜欢加入 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])
...
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>
重点:💡组件卸载时,不要忘记移除事件监听器!
请查看我们在 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
