如何使用CSS制作这种照明效果


125

我想模拟一个“扫描”灯,该灯将显示框中的单词,这是我现在的代码:

const e = document.getElementsByClassName('scan')[0];
document.onmousemove = function(event){
  e.style.left = `${event.clientX}px`;
};
*{
    margin: 0;
    padding: 0;
}

html, body{
    width: 100%;
    min-height: 100vh;
    overflow-x: hidden;
    
    display: flex;
}

.banner{
    width: 100vw;
    height: 100vh;

    display: flex;
    flex-grow: 1;
    flex-direction: row;
    align-items: center;
    background-color: #031321;
}

.banner .scan{
    width: 7px;
    height: 80%;
    
    position: absolute;
    left: 30px;
    z-index: 3;

    transition: left 50ms ease-out 0s;
    
    border-radius: 15px;
    background-color: #fff;
    box-shadow:
        0 0 15px 5px #fff,  /* inner white */
        0 0 35px 15px #008cff, /* inner blue */
        0 0 350px 20px #0ff; /* outer cyan */
}

.banner .description{
    width: 100%;
    color: white;
    font-size: 3em;
    text-align: center;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
<div class="banner">
    <div class="scan"></div>
    <div class="description">
        Just trying something
    </div>
</div>

想法是.description根据扫描灯的位置在div中显示单词。如果可能的话,我只想使用CSS来产​​生这种效果,并且只使用JavaScript来移动扫描(这将进一步变成CSS动画)。我尝试使用一些伪元素,但效果不佳。这是此动画应如何工作的示例。

Answers:


114

您可以将透明文本与渐变背景一起使用。我使用background-attachment: fixed了CSS变量来控制背景位置。

您可以增加背景尺寸(在此示例中为500px)以增加过渡平滑度。

const e = document.getElementsByClassName('scan')[0];
const hidden = document.getElementsByClassName('hidden')[0];

document.onmousemove = function(event) {
  e.style.left = `${event.clientX}px`; //               ↓ background width (500px) / 2
  hidden.style.setProperty("--pos", `${event.clientX - 250}px`);
};
* {
  margin: 0;
  padding: 0;
}

html,
body {
  width: 100%;
  min-height: 100vh;
  overflow-x: hidden;
  display: flex;
}

.banner {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-grow: 1;
  flex-direction: row;
  align-items: center;
  background-color: #031321;
}

.banner .scan {
  width: 7px;
  height: 80%;
  position: absolute;
  left: 30px;
  z-index: 3;
  transition: left 50ms ease-out 0s;
  border-radius: 15px;
  background-color: #fff;
  box-shadow: 0 0 15px 5px #fff, /* inner white */
  0 0 35px 15px #008cff, /* inner blue */
  0 0 350px 20px #0ff;
  /* outer cyan */
}

.banner .description {
  width: 100%;
  color: white;
  font-size: 3em;
  text-align: center;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.hidden {
  background: radial-gradient(dodgerblue 10%, #031321 50%) var(--pos) 50% / 500px 500px no-repeat fixed;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
<div class="banner">
  <div class="scan"></div>
  <div class="description">
    Just <span class="hidden">hidden</span> something
  </div>
</div>

这是另一个段落很长且包含多个隐藏文本的示例。我们在这里控制X和Y轴。

const hiddens = document.querySelectorAll('.hidden');

document.addEventListener("mousemove", e => {
  hiddens.forEach(p => {
    //                                            ↓ background width (400px) / 2
    p.style.setProperty("--posX", `${e.clientX - 200}px`);
    p.style.setProperty("--posY", `${e.clientY - 200}px`);
  });
});
html,
body {
  width: 100%;
  overflow-x: hidden;
}

body {
  background: #031321;
  color: #fff;
  font-size: 3rem;
  line-height: 1.5;
  padding: 20px;
  box-sizing: border-box;
}

.hidden {
  background: radial-gradient(dodgerblue 10%, #031321 50%) var(--posX) var(--posY) / 400px 400px no-repeat fixed;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
Lorem ipsum dolor sit amet, <span class="hidden">consectetur</span> adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <span class="hidden">Excepteur sint</span> occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum
dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea <span class="hidden">commodo</span> consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
id est laborum.


50

这是使用转换来获得更好性能的想法

要将其仅用在几个单词上,您可以玩 z-index


使它可在任何背景下使用的另一种想法,以防需要透明:


35

酷发光棒!

我假设这是一个徽标,并且当荧光棒通过文本后,该文本应继续显示。


我将在description元素上使用伪元素,将其放在顶部,并使用从透明色到深蓝色背景色的渐变背景。通过使用渐变,可以使文本获得很好的淡入效果。

然后,我将使用通过您的onmousemove方法更新的CSS变量设置深色背景颜色的起点。

该代码没有考虑不同的屏幕尺寸,因此,如果您希望动画具有响应性,则可能需要将像素转换为百分比。

我也将您的班级更改为id。我认为使用id显示该元素以某种方式由javascript使用更为合适。将元素绑定到变量也更容易。

const scanEl = document.getElementById('scan');
const descEl = document.getElementById("description")

document.onmousemove = function(event){
  let descriptionDisplacement = 100;
  scanEl.style.left = `${event.clientX}px`;
  descEl.style.setProperty("--background-shift", `${event.clientX + descriptionDisplacement}px`);
};
*{
    margin: 0;
    padding: 0;
}

html, body{
    width: 100%;
    min-height: 100vh;
    overflow-x: hidden;
    
    display: flex;
}

.banner{
    width: 100vw;
    height: 100vh;

    display: flex;
    flex-grow: 1;
    flex-direction: row;
    align-items: center;
    background-color: #031321;
}

.banner > #scan{
    width: 7px;
    height: 80%;
    
    position: absolute;
    left: 30px;
    z-index: 3;

    transition: left 50ms ease-out 0s;
    
    border-radius: 15px;
    background-color: #fff;
    box-shadow:
        0 0 15px 5px #fff,  /* inner white */
        0 0 35px 15px #008cff, /* inner blue */
        0 0 350px 20px #0ff; /* outer cyan */
}

.banner > #description{
    width: 100%;
    color: white;
    font-size: 3em;
    text-align: center;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
    
    /* ADDED */
    --background-shift: 0px;
    --background-shift-transparent: calc(var(--background-shift) - 150px);
    
    position: relative;
}

.banner > #description::before {
  content: '';
  background: linear-gradient(to right, transparent var(--background-shift-transparent), #031321 var(--background-shift));
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
<div class="banner">
    <div id="scan"></div>
    <div id="description">
        Just trying something
    </div>
</div>


17

我刚试过clipPath。从技术上讲,它可以满足您的需要,但与发光效果结合使用时,动画clipPath的性能会很差(但如果不使用,效果会更好)。可能是通过图像而不是盒子阴影来构建发光效果,从而可以改善这一点。(因为可以减小最外面的盒子阴影的大小)

const e = document.getElementsByClassName('scan')[0];
const description = document.getElementsByClassName('description')[0];
document.onmousemove = function(event){
    // comment out to compare performance
    e.style.left = `${event.clientX}px`;
    description.style.clipPath = `polygon(0 0, ${event.clientX}px 0, ${event.clientX}px 100%, 0 100%)`;
};
*{
    margin: 0;
    padding: 0;
}

html, body{
    width: 100%;
    min-height: 100vh;
    overflow-x: hidden;
    
    display: flex;
}

.banner{
    width: 100vw;
    height: 100vh;


    display: flex;
    flex-grow: 1;
    flex-direction: row;
    align-items: center;
    background-color: #031321;
}

.banner .scan{
    width: 7px;
    height: 80%;

    
    position: absolute;
    left: 30px;
    z-index: 3;

    transition: left 50ms ease-out 0s;
    
    border-radius: 15px;
    background-color: #fff;
    box-shadow:
        0 0 15px 5px #fff,  /* inner white */
        0 0 35px 15px #008cff, /* inner blue */
        0 0 350px 20px #0ff; /* outer cyan */
}

.banner .description{
    width: 100%;
    color: white;
    font-size: 3em;
    text-align: center;


    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
<div class="banner">
    <div class="scan"></div>
    <div class="description">
        Just trying something
    </div>
</div>


1
我喜欢这种解决方案,很遗憾使用会使动画变得沉重clip-path,尤其是因为最终我将只从短语中隐藏几个单词,因此使用剪辑需要更多项目,但是无论如何都要谢谢!刚刚在问题中添加了动画应如何工作的示例,可能会有所帮助。
Leo Letto

1
不幸的是,这只有在鼠标进入视口后才能起作用。如果它进入右侧的视口,即使您尚未“滚动”该文本,该文本仍然保持可见。
TylerH

1
@TylerH:我的帖子并不是要提供完整的解决方案,而是作为一个想法,当然可以将默认的剪辑路径添加到CSS中,但这不是重点。事实证明,所需的行为与我的理解有所不同
Ilmarinen123

9

尝试这样:

const e = document.getElementsByClassName('cover')[0];

e.addEventListener('click', animate);

function animate() {
    e.classList.add('scanning');
}
*{
    margin: 0;
    padding: 0;
}

html, body{
    width: 100%;
    min-height: 100vh;
    overflow-x: hidden;
    
    display: flex;
}

.banner{
    width: 100vw;
    height: 100vh;

    display: flex;
    flex-grow: 1;
    flex-direction: row;
    align-items: center;
    background-color: #031321;
}

.banner .cover{
    
    position: absolute;
    left: 30px;
    z-index: 3;
    height: 80%;
  width:100vw;
    background-color: #031321;
    transition: left 700ms ease-out 0s;
}

.banner .cover.scanning {
  left: calc(100% - 30px);
}

.banner .scan{
    width: 7px;
    height:100%;

    transition: left 50ms ease-out 0s;
    
    border-radius: 15px;
    background-color: #fff;
    box-shadow:
        0 0 15px 5px #fff,  /* inner white */
        0 0 35px 15px #008cff, /* inner blue */
        0 0 350px 20px #0ff; /* outer cyan */
}

.banner .description{
    width: 100%;
    color: white;
    font-size: 3em;
    text-align: center;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
<div class="banner">
  <div class="cover">
    <div class="scan">
    </div>
  </div>
    <div class="description">
        Just trying something
    </div>
</div>

该解决方案使用扫描右侧的封面,其背景颜色与横幅相同。封面随扫描一起移动,因此当扫描向右移动时,它会显示左侧的文本。通过在本演示中单击它可以工作,但是您可以使用JavaScript初始化它,但是最适合您。


2
完全显示/隐藏鼠标单击似乎不符合OP希望的基于光标位置移动条/隐藏文本的输出。
TylerH

同意 这就是我所说的“扫描”和“仅使用JavaScript移动扫描”的意思。
sbgib

5

似乎有些棘手。我想到的第一个解决方案可能是使用在灯条位置带有动态“停止点”的线性渐变。渐变从暗->透明(灯条位置)->暗开始。该代码可能看起来像:

.description-overlay {
  /*
    Replace 50% with the position of the light bar. Get brighter and more 
    transparent as you approach the position of the light bar.
  */
  background: linear-gradient(to right, #000, 50% hsla(0, 0%, 100%, 0.2), #000);
}

不知道这是否行得通,并且可能需要在某个地方扔一个盒子阴影,但是也许会给您一些想法。


刚刚在问题中添加了动画应如何工作的示例,可能会有所帮助。
Leo Letto
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.