让我们看看如何获取用户的鼠标位置并将其映射到 CSS 自定义属性:--positionX
和 --positionY
。
我们可以 在 JavaScript 中执行此操作。 如果我们这样做了,我们可以做一些事情,比如使 元素可拖动 或者 移动背景。 但实际上,我们仍然可以做类似的事情,但不用任何 JavaScript!
我将此方法用作我为获得 ‘单击并拖动’ 纯 CSS 效果制作的演示的一部分。 我使用了 我之前文章 中的 perspective
提示。 这是一个非常不错的效果,可以在 CSS 中完全实现,它可能比我的演示有更广泛的用途,所以让我们看一下。
设置
我们的第一个演示将使用 --positionX
和 --positionY
自定义属性来设置元素的 width
和 height
。
提醒您,我们在这里只使用 SCSS 来简短起见,但所有这些都可以在纯 CSS 中完成。
这是我们的初始状态。 我们这里有一个带有 .content
类的‘包装器’ <div>
,该类扩展到 body 的宽度和高度。 此 <div>
将承载我们项目的内容,以及我们想要使用鼠标位置控制的元素 - 在这种情况下,是 .square
元素。
我们还将两个自定义属性添加到 content
中。 我们将使用鼠标位置来设置这些属性的值,然后使用它们来相应地设置 .square
元素的 width
和 height
。
一旦我们映射了鼠标位置的自定义属性,我们就可以使用它们来执行我们想要的任何操作。 例如,我们可以使用它们来设置 absolute
定位的元素的 top
/ left
属性,控制 transform
属性,设置 background-position
,操作颜色,甚至设置伪元素的内容。 我们将在文章末尾看到一些这些额外演示。
网格
目标是在屏幕上创建一个不可见的网格,并使用 :hover
伪类将每个‘单元格’映射到我们将分配给自定义属性的一组值。 因此,当鼠标光标移到屏幕右侧时,--positionX
的值会更高; 当它移到左侧时,它会更低。 我们将对 --positionY
做同样的事情:当光标移到顶部时,该值会更低,当它移到底部时,该值会更高。
关于我们正在使用的网格大小,我们要说几句话:我们实际上可以将网格的大小设置为我们想要的任何大小。 网格越大,我们的自定义属性值就越精确。 但这也意味着我们将有更多单元格,这会导致性能问题。 重要的是要保持适当的平衡,并将网格大小调整到每个项目的特定需求。
现在,假设我们想要一个 10×10 的网格,在我们的标记中总共有 100 个单元格。(是的,使用 Pug 来实现这一点是可以的,即使我在本示例中不会这样做。)
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<!-- 97 more cells -->
<div class="content">
<div class="square"></div>
</div>
您可能想知道为什么 .cell
元素出现在 .content
之前。 那是因为级联。
我们想要使用 .cell
类来控制 .square
,级联的工作方式(目前)是元素只能控制其子元素(或后代)及其兄弟姐妹(或其后代) - 但只有当兄弟姐妹位于控制元素之后时才有效。
这意味着两件事
- 每个
.cell
必须位于我们想要控制的元素(在本例中为.square
)之前。 - 我们不能将这些
.cell
放入容器中,因为如果我们这样做,.content
就不是它们的兄弟姐妹了。
定位单元格
有几种方法可以定位 .cell
。 我们可以使用 position: absolute
将它们定位,并使用 top
和 left
属性偏移它们。 或者我们可以使用 transform
将它们平移到位置。 但最简单的方法可能是使用 display: grid
。
body {
background-color: #000;
height: 100vh;
display: grid;
grid-template: repeat(10, 1fr) / repeat(10, 1fr);
}
.cell {
width: 100%;
height: 100%;
border: 1px solid gray;
z-index: 2;
}
border
只是暂时的,所以我们可以看到屏幕上的cell
。 我们稍后会将其删除。z-index
很重要,因为我们希望cell
位于content
的前面。
这是我们到目前为止所拥有的内容
添加值
我们想要使用 .cell
来设置 --positionX
和 --positionY
值。
当我们悬停在第一列(左侧)的 .cell
上时,--positionX
的值应为 0
。 当我们悬停在第二列的 .cell
上时,该值应为 1
。 在第三列中应为 2
,依此类推。
y 轴也是如此。 当我们悬停在第一行(顶部)的 .cell
上时,--positionY
应为 0
,当我们悬停在第二行的 cell
上时,该值应为 1
,依此类推。

此图像中的数字表示网格中单元格元素的编号。 如果我们以单个 .cell
为例 - 假设是第 42 个单元格 - 我们可以使用 :nth-child()
来选择它
.cell:nth-child(42) { }
但我们需要记住几件事
- 我们只希望此选择器在我们将鼠标悬停在单元格上时有效,因此我们将
:hover
附加到它。 - 我们想要选择
.content
而不是单元格本身,因此我们将使用 通用兄弟选择器 (~
) 来执行此操作。
因此,现在,要将 --positionX
设置为 1
,将 --positionY
设置为 3
,以便在将鼠标悬停在第 42 个 cell
上时为 .content
设置这些值,我们需要执行以下操作
.cell:nth-child(42):hover ~ .content {
--positionX: 1;
--positionY: 3;
}
但是谁想做 100 次呢!? 有几种方法可以简化操作
- 使用 Sass
@for
循环 遍历所有 100 个单元格,并进行一些计算以在每次迭代中设置正确的--positionX
和--positionY
值。 - 分别使用 x 轴和 y 轴,分别使用
:nth-child
的 函数式表示法 来单独选择每一行和每一列。 - 将这两种方法结合起来,使用 Sass
@for
循环以及:nth-child
的函数式表示法。
我认真思考了哪种方法是最好的、最简单的方法,虽然它们都有优缺点,但我最终选择了第三种方法。 要编写的代码量、编译代码的质量以及数学复杂度都影响了我的思考。 不同意? 在评论中告诉我原因!
@for
循环设置值
使用 @for $i from 0 to 10 {
.cell:nth-child(???):hover ~ .content {
--positionX: #{$i};
}
.cell:nth-child(???):hover ~ .content {
--positionY: #{$i};
}
}
这是基本循环。 我们要循环 10 次,因为我们有 10 行和 10 列。 我们已经将 x 轴和 y 轴分开,分别为每一列设置 --positionX
,为每一行设置 --positionY
。 我们现在只需要用适当的表示法替换那些 ???
东西来选择每一行和每一列。
让我们从 x 轴开始
回到我们的网格图像(带有数字的那个),我们可以看到第二列中所有单元格的编号都是 10 的倍数,再加上 2。 第三列中的单元格是 10 的倍数,再加上 3。 依此类推。
现在让我们将其‘翻译’成 :nth-child
的函数式表示法。 以下是第二列的表示方式
:nth-child(10n + 2)
10n
选择 10 的所有倍数。2
是列号。
对于我们的循环,我们将用 #{$i + 1}
替换列号,以便按顺序进行迭代
.cell:nth-child(10n + #{$i + 1}):hover ~ .content {
--positionX: #{$i};
}
现在我们来处理y轴
再次查看网格图像,并关注第四行。单元格编号介于 41 和 50 之间。第五行的单元格介于 51 到 60 之间,依此类推。要选择每一行,我们需要定义它的范围。例如,第四行的范围是
.cell:nth-child(n + 41):nth-child(-n + 50)
(n + 41)
是范围的起始位置。(-n + 50)
是范围的结束位置。
现在我们将用$i
值的一些数学运算替换数字。对于范围的开始,我们得到(n + #{10 * $i + 1})
,对于范围的结束,我们得到(-n + #{10 * ($i + 1)})
。
所以最终的@for
循环是
@for $i from 0 to 10 {
.cell:nth-child(10n + #{$i + 1}):hover ~ .content {
--positionX: #{$i};
}
.cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content {
--positionY: #{$i};
}
}
映射完成!当我们悬停在元素上时,--positionX
和--positionY
根据鼠标位置改变。这意味着我们可以用它们来控制.content
内部的元素。
处理自定义属性
好的,现在我们已经将鼠标位置映射到两个自定义属性,接下来我们要用它们来控制.square
元素的width
和height
值。
我们先从width
开始,假设我们想要.square
的最小width
为100px
(即当鼠标光标位于屏幕左侧时),并且我们希望它每当鼠标光标向右移动一步就增长20px
。
使用calc()
,我们可以做到
.square {
width: calc(100px + var(--positionX) * 20px);
}
当然,我们也会对height
做同样的事情,但使用--positionY
而不是--positionX
.square {
width: calc(100px + var(--positionX) * 20px);
height: calc(100px + var(--positionY) * 20px);
}
就这样!现在我们有一个简单的.square
元素,它的width
和height
由鼠标位置控制。将鼠标光标移动到窗口上,观察square
如何根据鼠标位置改变它的width
和height
。
我添加了一个小的transition
来使效果更平滑。当然,这不是必需的。我还在.cell
边框上添加了注释。
让我们尝试另一种方法
可能会有这样一种情况,你想要“绕过”--positionX
和--positionY
,并在@for
循环中直接设置结束值。所以,对于我们的示例,它看起来像这样
@for $i from 0 to 10 {
.cell:nth-child(10n + #{$i + 1}):hover ~ .content {
--squareWidth: #{100 + $i * 20}px;
}
.cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content {
--squareHeight: #{100 + $i * 20}px;: #{$i};
}
}
那么.square
将像这样使用自定义属性
.square {
width: var(--squareWidth);
height: var(--squareHeight);
}
这种方法稍微灵活一些,因为它允许使用更高级的Sass数学(和字符串)函数。也就是说,基本原理与我们已经介绍的完全相同。
下一步是什么?
好吧,剩下的就由你决定了——而且可能性是无限的!你认为你会如何使用它?你能进一步扩展它吗?尝试在你的CSS中使用这个技巧,并在评论中分享你的作品,或者在Twitter上告诉我。看到这些作品汇集在一起会很棒。
这里有一些例子可以让你开始思考
你好,我非常喜欢你的方法和创意。但我无法解决的一个问题是,当将这种技术应用到移动版本时,因为移动版本几乎没有
:hover
。我希望你能在后面作为评论或甚至是一篇完整的文章详细阐述这一部分,当然如果你有时间的话。但无论如何,谢谢你提供这个想法。首先,谢谢你。
不幸的是,正如你所说,触控控制没有
:hover
,所以这种技术对于移动设备不太相关。在这种情况下,你可以使用JS。确实触屏设备上没有
:hover
,但有:focus
。虽然我还没有测试过,但我认为如果你在网格的每个<div>
中添加tabindex="0"
,你应该能够使用:focus
(除了:hover
)让它在触屏设备上工作。然而,不清楚用手指在屏幕上拖动是否会导致焦点跟随你的手指。所以触控几乎肯定会起作用,但拖动可能不会。大约两年前我写过关于这个(用纯CSS跟随鼠标)的内容。
https://kawalekkodu.pl/follow-the-white-mouse-czyli-o-poruszaniu-obiektu-za-mysza-w-css
看起来很棒的文章。
不幸的是我不懂波兰语,所以没有读过它,但我下周将在华沙的一个聚会上谈论这个话题,欢迎你来参加。:)
提醒一下:很多这样的例子,以及其他类似的例子,在移动设备上根本无法使用。我知道它们本身已经很复杂了,但包括一些关于在没有
hover
伪类的条件下哪些内容可以使用的说明,可以真正帮助读者评估是他们的浏览器不支持,还是例子本身有问题。不知道在生产环境中这到底有多实用,但确实很酷!
关于移动设备和
hover
的问题:它在移动设备上是模拟的(部分或全部?),通过按住触控(“按下”),但我猜你可能可以用按钮替换div
网格…用自定义属性可以实现令人难以置信的效果,非常棒的技术。
在我的移动设备上的 Chromium 浏览器中可以正常工作,但可能将所有内容都应用于
:focus
也能帮助触控支持。好吧,我应该在发布我的评论之前阅读你的评论:-)
很棒的演示,我喜欢你在这项工作之上进行构建的方式(不知道是否故意)https://translate.google.com/translate?sl=pl&tl=en&u=https://kawalekkodu.pl/follow-the-white-mouse-czyli-o-poruszaniu-obiektu-za-mysza-w-css
矩阵的想法已经存在了,但你用变量添加了一个有趣的层,这在某些情况下可以使它更易于维护:)
如果不是黑客、骗子和此类威胁,我们现在应该已经在 CSS 中拥有纯 CSS 鼠标位置、键盘跟踪和其他与硬件相关的符号了。
例如,我们拥有所有你能想到的奇特的过滤、颜色校正、字体选择、混合、嵌入图像和变形!拥有位置变量将弥合差距,使 CSS 更加强大!
但它永远不会发生。由于利用者,安全性将始终处于每个发明的标准的最前沿。衷心感谢那些只利用他人努力的人。当然,这是讽刺。
我很难相信人类能永远达到这样一种境界,即我们不会抓住一切机会利用一切,每个人都始终保持适当的克制。想象一下,如果没有所有安全膨胀,我们的技术可以做些什么。我们不能只说一句话,让他们都改变,真是太可惜了。
所以,我们有这个示例,必须绕过它们。我赞扬你,但不幸的是,如果你是一支团队的成员,你通常不允许自己跨越 CSS-HTML 的界限,必须请求 HTML 开发人员为你添加变量,如果你真的可以的话。我们必须这样做,真是太烦人了。否则,我们必须想出其他方法,或者向我们的老板解释为什么只用 CSS 是不可能的。然后我们回家诅咒利用者,就像我刚才做的那样。