一、过程分析
先上效果:
在Windows系统里有一个很棒的细节效果,元素的渐变高亮边框是可以感知鼠标的,边框的高亮部分会跟随鼠标的移动而移动。
这种效果也是比较常见的,但是实现起来还是需要一点时间和思路的。
首先,我们先完成基础的布局。代码如下:
<div class="container">
<div class="card">
<div class="inner">
<p>黄鹤楼送孟浩然之广陵</p>
<p>唐 · 李白</p>
<p>故人西辞黄鹤楼,烟花三月下扬州。</p>
<p>孤帆远影碧空尽,唯见长江天际流。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>送元二使安西</p>
<p>唐 · 王维</p>
<p>渭城朝雨浥轻尘,客舍青青柳色新。</p>
<p>劝君更尽一杯酒,西出阳关无故人。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>春日</p>
<p>宋 · 朱熹</p>
<p>胜日寻芳泗水滨,无边光景一时新。</p>
<p>等闲识得东风面,万紫千红总是春。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>小池</p>
<p>唐 · 杨万里</p>
<p>泉眼无声惜细流,树阴照水爱晴柔。</p>
<p>小荷才露尖尖角,早有蜻蜓立上头。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>村居</p>
<p>清 · 高鼎</p>
<p>草长莺飞二月天,拂堤杨柳醉春烟。</p>
<p>儿童散学归来早,忙趁东风放纸鸢。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>晓出净慈寺送林子方</p>
<p>唐 · 杨万里</p>
<p>毕竟西湖六月中,风光不与四时同。</p>
<p>接天莲叶无穷碧,映日荷花别样红。</p>
</div>
</div>
</div>
body {
background-color: black;
}
p {
margin: 0;
line-height: 2;
}
.container {
display: grid;
width: 100%;
margin-top: 2rem;
color: #f0f0f0;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
text-align: center;
}
.card {
aspect-ratio: 4/2;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.2);
}
.inner {
background: #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
接下来给 .card 加上相对定位position: relative属性,给 .inner 加上绝对定位position: absolute属性,让card盒子和inner盒子完全重叠在一起。
.card {
aspect-ratio: 4/2;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.2);
position: relative;
}
.inner {
position: absolute;
inset: 10px;
background: #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
为了方便观察,给 .inner 设置 inset: 10px 属性,此属性相当于 left: 10px;top: 10px;right: 10px; bottom: 10px;。
效果如下:
想要实现高亮的效果,我们还需要在 .card 和 .inner 之间再插入一层,这一层是我们实现高亮的关键!这三层盒子一样大小。
.card::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(closest-side circle, #fff, transparent);
}
.inner {
position: absolute;
inset: 10px;
/* background: #222; */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
为了便于观察,先把.inner的背景色去掉。效果如下:
看到这里有的人可能已经知道这个中间层用来干什么的了。没错,这不就是一个渐变高亮的效果嘛!我们再在 .card::before 加上一个 transform属性,同时在 .card 上加上 overflow: hidden属性,观察浏览器,神奇的效果出现了!我悟了!
.card {
aspect-ratio: 4/2;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
}
.card::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(closest-side circle, #fff, transparent);
transform: translate(70px, 80px);
}
.inner {
position: absolute;
inset: 10px;
background: #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
效果如下:
原来如此,一个渐变高亮的边框效果不就实现了嘛!
接下来就是用js代码实现 .card::before 中间层跟随鼠标移动即可。
假如图中的小圆圈为鼠标位置,当鼠标移动到此位置时,只需要计算把每张卡片中间层的中心偏移到鼠标的位置即可。那怎么去计算呢?
首先,获取到每张卡片的元素,监听鼠标移动onmousemove事件,鼠标移动时循环遍历每个卡片元素给每个卡片设置transform: translate(x, y)即可。
x(偏移的水平距离) = 鼠标离视口的水平距离 - 元素离视口的水平距离 - 元素宽度的一半。
y(偏移的垂直距离) = 鼠标离视口的垂直距离 - 元素离视口的垂直距离 - 元素高度的一半。
代码如下:
const container = document.querySelector('.container');
const cards = document.querySelectorAll('.card');
container.onmousemove = e => {
for(const card of cards) {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
card.style.setProperty('--x', `${x}px`);
card.style.setProperty('--y', `${y}px`);
}
}
二、完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>b.html</title>
<style type="text/css">
body {
background-color: black;
}
p {
margin: 0;
line-height: 2;
}
.container {
display: grid;
width: 90%;
margin: 2rem auto 0;
color: #f0f0f0;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
text-align: center;
}
.card {
aspect-ratio: 4/2;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
}
.card::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(closest-side circle, #fff, transparent);
/* 偏移到看不到的地方 */
transform: translate(var(--x, -10000px), var(--y, -10000px));
}
.inner {
position: absolute;
inset: 10px;
background: #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="inner">
<p>黄鹤楼送孟浩然之广陵</p>
<p>唐 · 李白</p>
<p>故人西辞黄鹤楼,烟花三月下扬州。</p>
<p>孤帆远影碧空尽,唯见长江天际流。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>送元二使安西</p>
<p>唐 · 王维</p>
<p>渭城朝雨浥轻尘,客舍青青柳色新。</p>
<p>劝君更尽一杯酒,西出阳关无故人。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>春日</p>
<p>宋 · 朱熹</p>
<p>胜日寻芳泗水滨,无边光景一时新。</p>
<p>等闲识得东风面,万紫千红总是春。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>小池</p>
<p>唐 · 杨万里</p>
<p>泉眼无声惜细流,树阴照水爱晴柔。</p>
<p>小荷才露尖尖角,早有蜻蜓立上头。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>村居</p>
<p>清 · 高鼎</p>
<p>草长莺飞二月天,拂堤杨柳醉春烟。</p>
<p>儿童散学归来早,忙趁东风放纸鸢。</p>
</div>
</div>
<div class="card">
<div class="inner">
<p>晓出净慈寺送林子方</p>
<p>唐 · 杨万里</p>
<p>毕竟西湖六月中,风光不与四时同。</p>
<p>接天莲叶无穷碧,映日荷花别样红。</p>
</div>
</div>
</div>
</body>
<script type="text/javascript">
const container = document.querySelector('.container');
const cards = document.querySelectorAll('.card');
container.onmousemove = e => {
for(const card of cards) {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
card.style.setProperty('--x', `${x}px`);
card.style.setProperty('--y', `${y}px`);
}
}
</script>
</html>