124
votes

Comment créer cet effet d'éclairage avec CSS

Je voudrais simuler une lumière "scan" qui révélera des mots dans une boîte, voici mon code maintenant:

<div class="banner">
    <div class="scan"></div>
    <div class="description">
        Just trying something
    </div>
</div>
*{
    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;
}
const e = document.getElementsByClassName('scan')[0];
document.onmousemove = function(event){
  e.style.left = `${event.clientX}px`;
};

L'idée est de montrer les mots dans le .description div en fonction de la position de la lumière de balayage. Si possible, j'aimerais utiliser CSS uniquement pour créer cet effet, et utiliser JavaScript uniquement pour déplacer l'analyse (qui deviendra plus tard une animation CSS). J'ai essayé d'utiliser des pseudo éléments, mais cela n'a pas bien fonctionné. Voici un exemple du fonctionnement de cette animation.


0 commentaires

6 Réponses :


5
votes

Cela semble un peu délicat. La première solution qui me vient à l'esprit est peut-être d'utiliser un dégradé linéaire avec un "point d'arrêt" dynamique à la position de la barre lumineuse. Le dégradé va de sombre -> transparent (position de la barre lumineuse) -> sombre. Le code ressemblera peut-être à quelque chose comme:

.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);
}

Je ne sais pas si cela fonctionnera et il faudrait probablement qu'une boîte-ombre soit projetée quelque part, mais peut-être que cela vous donnera des idées.


1 commentaires

Juste ajouté à la question un exemple de la façon dont l'animation devrait fonctionner, cela peut aider.



17
votes

Je viens d'essayer clipPath. Techniquement, il fait ce dont vous avez besoin, mais les performances du clipPath animé sont assez médiocres lorsqu'il est combiné avec l'effet de lueur (mais beaucoup mieux sans!). La construction de la lueur à partir de quelque chose comme une image au lieu d'une boîte-ombre améliorerait cela. (comme pourrait réduire la taille de l'ombre de la boîte la plus extérieure)

<div class="banner">
    <div class="scan"></div>
    <div class="description">
        Just trying something
    </div>
</div>
*{
    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;
}
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%)`;
};


3 commentaires

J'ai aimé cette solution, triste que l'animation devienne lourde en utilisant le clip-path , surtout parce que finalement je ne cacherai que quelques mots d'une phrase, donc cela nécessiterait plus d'éléments en utilisant le clip, mais merci vous en tout cas! Juste ajouté à la question un exemple de la façon dont l'animation devrait fonctionner, cela peut aider.


Cela ne fonctionne malheureusement que lorsque la souris entre dans la fenêtre. Et s'il entre dans la fenêtre sur le côté droit, le texte reste visible même si vous ne l'avez pas encore fait défiler.


@TylerH: mon message n'était pas conçu comme une solution complète mais plutôt comme une idée, bien sûr, un chemin de clip par défaut peut être ajouté au css mais ce n'était pas le but ici. Il s'est également avéré que le comportement souhaité était différent de ce que j'avais compris



9
votes

Essayez comme ceci:

<div class="banner">
  <div class="cover">
    <div class="scan">
    </div>
  </div>
    <div class="description">
        Just trying something
    </div>
</div>
*{
    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;
}
const e = document.getElementsByClassName('cover')[0];

e.addEventListener('click', animate);

function animate() {
    e.classList.add('scanning');
}

Cette solution utilise une couverture à droite de la numérisation avec la même couleur d'arrière-plan que la bannière. La couverture se déplace avec le scan, donc quand le scan se déplace vers la droite, il révèle le texte sur la gauche. Cela fonctionne en cliquant dessus dans cette démo, mais vous pouvez l'initier en JavaScript, mais c'est le mieux pour vous.


2 commentaires

La révélation / masquage complet sur clic de souris ne semble pas correspondre à la sortie souhaitée par OP consistant à déplacer la barre / masquer le texte en fonction de la position du curseur.


D'accord. C'est exactement ce que j'ai pris pour «scanner» et «utiliser JavaScript uniquement pour déplacer l'analyse».



35
votes

Cool bâton lumineux!

Je suppose que c'est pour un logo, et que le texte doit continuer à être affiché lorsque le bâton lumineux a passé le texte.


J'utiliserais un pseudo-élément sur l'élément de description, le placerais sur le dessus et utiliserais un fond dégradé allant du transparent à la couleur de fond bleu foncé. En utilisant un dégradé, vous pouvez obtenir un joli fondu du texte.

Je définirais ensuite le point de départ de la couleur de fond sombre avec une variable CSS que je mets à jour via votre méthode onmousemove.

Le code ne prend pas en compte les différentes tailles d'écran, vous devez donc probablement convertir les pixels en pourcentage, si vous voulez que votre animation soit réactive.

J'ai également changé vos classes en id. Je pense qu'il est plus approprié de montrer, en utilisant des identifiants, que l'élément est en quelque sorte utilisé par javascript. Il est également plus facile de lier les éléments à des variables.

<div class="banner">
    <div id="scan"></div>
    <div id="description">
        Just trying something
    </div>
</div>
*{
    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;
}
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`);
};


0 commentaires

114
votes

Vous pouvez utiliser du texte transparent avec un fond dégradé. J'ai utilisé background-attachment: fixed et une variable CSS pour contrôler la position de l'arrière-plan.

Vous pouvez augmenter la taille de l'arrière-plan (500 px dans cet exemple) pour augmenter le lissage de la transition.

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.
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;
}
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`);
  });
});

Voici un autre exemple avec un paragraphe très long et plusieurs textes masqués. Nous contrôlons les axes X et Y ici.

<div class="banner">
  <div class="scan"></div>
  <div class="description">
    Just <span class="hidden">hidden</span> something
  </div>
</div>
* {
  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;
}
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`);
};


0 commentaires

50
votes

Voici une idée utilisant la transformation pour avoir de meilleures performances

<div class="banner">
      Just <span>trying</span> something <span>cool</span>
</div>
body{
    margin: 0;
    overflow:hidden;
}

.banner{
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content:center;
    color: white;
    font-size: 3em;
    background: url(https://picsum.photos/id/1018/800/800) center/cover;
    position:relative;
    z-index:0;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.banner::before{
    content:"";
    width: 7px;
    height: 80%;
    position: absolute;
    left: 0;
    transform:translateX(var(--p,30px));
    z-index: 3;
    transition: transform 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::after {
   content:"";
   position:absolute;
   z-index:-1;
   top:0;
   right:0;
   bottom:0;
   left:0;
   background:inherit;
   -webkit-mask:
      linear-gradient(to right,#fff 45%,transparent,#fff 55%)
      right calc(-1*var(--p,0px)) top 0/200% 100% no-repeat;
}
.banner  > span {
  position:relative;
  z-index:-2;
  color:red;
  font-weight:bold;
}
document.onmousemove = function(event){
  document.body.style.setProperty("--p", `${event.clientX}px`);
};

Pour l'appliquer à seulement quelques mots, vous jouez avec z-index

<div class="banner">
    <div class="description">
        Just <span>trying</span> something <span>cool</span>
    </div>
</div>
body{
    margin: 0;
    overflow:hidden;
}

.banner{
    height: 100vh;
    display: flex;
    align-items: center;
    background-color: #031321;
}

.banner::before{
    content:"";
    width: 7px;
    height: 80%;
    position: absolute;
    left: 0;
    transform:translateX(var(--p,30px));
    z-index: 3;
    transition: transform 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{
    color: white;
    font-size: 3em;
    text-align: center;
    width:100%;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
    overflow:hidden;
    position:relative;
    z-index:0;
}
.banner .description::before {
   content:"";
   position:absolute;
   z-index:-1;
   top:0;
   right:0;
   bottom:0;
   width:200%;
   background:linear-gradient(to right,#031321 40%,transparent,#031321 60%);
   transform:translateX(var(--p,0px));
}
.banner .description > span {
  position:relative;
  z-index:-2;
  color:lightblue;
  font-weight:bold;
}
document.onmousemove = function(event){
  document.body.style.setProperty("--p", `${event.clientX}px`);
};

Une autre idée pour le faire fonctionner avec n'importe quel arrière-plan au cas où vous voudriez de la transparence:

<div class="banner">
    <div class="description">
        Just trying something
    </div>
</div>
body{
    margin: 0;
    overflow:hidden;
}

.banner{
    height: 100vh;
    display: flex;
    align-items: center;
    background-color: #031321;
}

.banner::before{
    content:"";
    width: 7px;
    height: 80%;
    position: absolute;
    left: 0;
    transform:translateX(var(--p,30px));
    z-index: 3;
    transition: transform 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{
    color: white;
    font-size: 3em;
    text-align: center;
    width:100%;

    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
    overflow:hidden;
    position:relative;
}
.banner .description::before {
   content:"";
   position:absolute;
   top:0;
   right:0;
   bottom:0;
   width:200%;
   background:linear-gradient(to right,#031321 40%,transparent,#031321 60%);
   transform:translateX(var(--p,0px));
}
document.onmousemove = function(event){
  document.body.style.setProperty("--p", `${event.clientX}px`);
};


0 commentaires