SVGGratuit

Text Mask Video

Une vraie vidéo qui ne se voit qu'à travers les lettres d'un titre : le texte reste du texte, la vidéo vit dedans, le bloc se fond dans un fond sombre.

CSSTextGradient

Mis à jour le

PLAY

Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie SVG en contient 30 au total, dont 3 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

DJ · house, disco, edits — basé à Lyon

VELMORAY

Prochaines dates

ven. 14 nov.Nantes · club23 h
sam. 22 nov.Lille · festival19 h
sam. 6 déc.Genève · rooftop21 h
Une date à proposer ?

Clubs, festivals, soirées privées. Réponse sous 48 h.

① Nom du DJ en tête de sa page de réservation

QuandLa page de réservation d'un DJ : « VELMORAY » en très grand sous la navigation, ses prochaines dates en bas à gauche et un encart « Une date à proposer ? » avec le bouton de réservation.
PourquoiLe nom est la première chose qu'un organisateur lit ; rempli par les images d'un set, il montre l'ambiance sans ajouter de lecteur vidéo à la page. Le bloc se fond dans le noir du site : on ne voit que les lettres.
Réglages--tmv-size:70px, --tmv-page:transparent, --tmv-height:0px ; clip de démonstration aux couleurs --tmv-c1:#ff5a36, --tmv-c2:#ff2e88, --tmv-c3:#ffb347, --tmv-c4:#8b3dff ; titre en <h1 class="tmv-text">. En production : src d'un extrait de set, sans data-tmv-demo.
G

Grimolte

12,4 k abonnés · jeux de plateforme

EN DIRECT

AccueilVidéosEn directÀ propos
depuis 2 h 10

Speedrun du dimanche — tous les mondes d'une traite

1 284 spectateurs · discussion ouverte

② Mot « EN DIRECT » sur la page d'une chaîne de streaming

QuandL'en-tête de la chaîne « Grimolte » pendant un direct : avatar, nom, abonnés, et à droite un point rouge suivi de « EN DIRECT » en 22 px, rempli par l'image du direct. Dessous, les onglets et la carte du direct en cours avec le bouton « Regarder ».
PourquoiUn badge qui bouge avec le direct dit « ça se passe maintenant » mieux qu'un texte rouge figé. À cette taille, la vidéo n'est qu'une texture de couleur : le mot reste lisible.
Réglages--tmv-size:22px ; teintes vert et cyan --tmv-c1:#10b981, --tmv-c2:#22d3ee, --tmv-c3:#a3e635, --tmv-c4:#3b82f6 ; texte en <p class="tmv-text">. En production, la vidéo peut recevoir le flux du direct par srcObject.
ciné-club du jeudi Programme de novembre

Survolez ou sélectionnez une affiche : la bande-annonce passe dans son titre.

Jeudi 20 h 30 · salle 2Entrée libre

③ Bande-annonce dans le titre de l'affiche survolée — ciné-club

QuandLe programme de novembre d'un ciné-club : trois affiches côte à côte. Au repos, chaque titre montre une image figée de son film ; au survol ou au focus clavier d'une affiche, sa bande-annonce se lance dans les lettres et s'arrête quand on la quitte.
PourquoiTrois vidéos qui tournent en même temps se disputeraient l'attention ; une seule à la fois, sous le pointeur, donne un aperçu sans ouvrir de lecteur. Les affiches sont de vrais liens : le focus clavier déclenche le même aperçu.
Réglages--tmv-size:20px, trois jeux de couleurs ; vidéos sans autoplay. Le script de la scène appelle play() puis pause() au premier affichage (image figée), play() sur pointerenter/focus et pause() sur pointerleave/blur. En mouvement réduit, l'effet refige la vidéo : le titre garde son image fixe.

Comment ça marche

Le bloc .tmv-stage empile trois couches : un dégradé de secours, la <video class="tmv-video"> qui couvre tout le bloc (object-fit:cover), puis le titre .tmv-text écrit en blanc sur un fond noir. Le titre est fusionné en mix-blend-mode:multiply : le blanc laisse passer la vidéo telle quelle, le noir l'éteint. Résultat : la vidéo ne reste visible que dans la forme des lettres.

Le bloc entier est ensuite fusionné avec la page en mix-blend-mode:screen, où le noir devient transparent. Le rectangle autour des lettres disparaît donc sur n'importe quel fond sombre, même une photo, sans avoir à recopier la couleur de la page. La classe tmv-light inverse les deux modes pour une page claire (texte noir, le blanc disparaît).

Vous mettez votre vidéo dans le HTML : retirez data-tmv-demo et ajoutez src="votre-video.mp4" (ou des <source> WebM + MP4). Tant que l'attribut data-tmv-demo est là et qu'aucune source n'est donnée, le script fabrique un clip de démonstration en local : un canvas 480 × 270 animé (taches de couleur et faisceaux lumineux aux couleurs --tmv-c1 à --tmv-c4) branché sur la vidéo par captureStream(). Aucun fichier n'est chargé.

Le dégradé de secours remplit les lettres tant que la vidéo n'a pas d'image (chargement lent) et pour de bon si elle échoue : fichier absent ou format refusé, le script pose tmv-failed et masque la vidéo, le titre reste lisible. Dès qu'une image s'affiche, la classe tmv-playing arrête le dégradé. Hors de l'écran, la vidéo se met en pause et une vidéo en lecture automatique reprend au retour.

Accessibilité

  • Vrai texte : le titre est un élément de texte normal (<h2> par défaut, n'importe quel bloc avec la classe tmv-text) : lu par les lecteurs d'écran, sélectionnable, traduit par le navigateur. La vidéo porte aria-hidden="true" et tabindex="-1" : elle est décorative et n'entre pas dans le parcours au clavier.
  • Mouvement réduit : avec prefers-reduced-motion: reduce, le script laisse la vidéo afficher sa première image puis la met en pause ; le dégradé de secours ne défile plus. Le réglage est suivi en cours de visite. Sans JavaScript, seul le dégradé s'arrête : une vidéo en autoplay continuerait.
  • Plus de 5 secondes de mouvement : une vidéo en boucle qui démarre seule doit pouvoir être arrêtée (critère WCAG 2.2.2). Si le titre reste longtemps à l'écran, ajoutez un vrai <button> qui appelle pause() sur la vidéo.
  • Lisibilité : les lettres prennent les couleurs de la vidéo. Une vidéo très sombre donne des lettres sombres sur fond sombre : choisissez un plan lumineux, une police épaisse, ou passez en variante claire.

Compatibilité navigateur

mix-blend-mode (multiply et screen), vidéo HTML5, inset, IntersectionObserver ; pour le clip de démonstration seulement : HTMLCanvasElement.captureStream() et srcObject. Mesuré dans Chromium, Firefox et WebKit (Playwright) : vraie vidéo MP4/WebM dans les lettres, clip de démonstration, vidéo absente (repli sur le dégradé), variante claire, deux copies, mouvement réduit, 390 px. Le WebKit de test refuse toute lecture automatique sans geste, même muette : la lecture a été vérifiée après un clic ; Safari autorise la lecture automatique des vidéos muettes avec playsinline.

Chrome 87+✓ Complet (mesuré)
Firefox 66+✓ Complet (mesuré)
Safari 14.1+✓ Rendu mesuré dans WebKit ; lecture automatique non vérifiée
Edge 87+✓ Complet (moteur Chromium)
Safari iOS 14.5+✓ Moteur WebKit, non vérifié sur appareil réel
Chrome Android 87+✓ Complet (moteur Chromium)

Vidéo qui ne charge pas, format non lu ou lecture automatique bloquée (mode économie d'énergie sur iPhone) : les lettres gardent le dégradé animé ou la première image, le titre reste lisible. Sans JavaScript, votre vidéo joue normalement ; seuls le clip de démonstration, la pause hors écran et l'arrêt en mouvement réduit manquent.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="fx-0619-root">
  <div class="tmv-stage">
    <video class="tmv-video" data-tmv-demo autoplay muted loop playsinline aria-hidden="true" tabindex="-1"></video>
    <h2 class="tmv-text">PLAY</h2>
  </div>
</div>
CSS
.fx-0619-root,
.fx-0619-root *,
.fx-0619-root *::before,
.fx-0619-root *::after {
  box-sizing: border-box;
}

.fx-0619-root {
  /* Réglages : taille du texte, fond du bloc, couleurs du repli et du clip de démo */
  --tmv-size: 4rem;
  --tmv-page: #0a0a0f;
  --tmv-height: 250px;
  --tmv-c1: #6366f1;
  --tmv-c2: #d946ef;
  --tmv-c3: #06b6d4;
  --tmv-c4: #f43f5e;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--tmv-height);
  overflow: hidden;
  position: relative;
  background: var(--tmv-page);
  color: #fff;
  font-family: sans-serif;
}

/* Le bloc se fond dans la page (mode screen : le noir disparaît) ; sous la vidéo, un dégradé qui défile tant qu’elle n’a pas d’image */
.fx-0619-root .tmv-stage {
  position: relative;
  max-width: 100%;
  overflow: hidden;
  background: #000;
  mix-blend-mode: screen;
}

.fx-0619-root .tmv-stage::before,
.fx-0619-root .tmv-video {
  position: absolute;
  inset: 2px;
  display: block;
  width: calc(100% - 4px);
  height: calc(100% - 4px);
  pointer-events: none;
}

.fx-0619-root .tmv-stage::before {
  content: "";
  background: linear-gradient(90deg, var(--tmv-c1), var(--tmv-c2), var(--tmv-c3), var(--tmv-c4), var(--tmv-c1)) 0 50% / 400% 100%;
  animation: tmvFlow 3s linear infinite;
}

.fx-0619-root .tmv-video {
  object-fit: cover;
}

/* Texte blanc sur noir en mode multiply : la vidéo ne reste visible que dans les lettres */
.fx-0619-root .tmv-text {
  position: relative;
  margin: 0;
  padding: .3em .45em;
  font-size: var(--tmv-size);
  font-weight: 900;
  line-height: 1;
  text-align: center;
  overflow-wrap: anywhere;
  color: #fff;
  background: #000;
  mix-blend-mode: multiply;
}

/* Variante pour page claire : modes inversés (le blanc disparaît) */
.fx-0619-root.tmv-light {
  --tmv-page: #fff;
}

.fx-0619-root.tmv-light .tmv-stage {
  background: #fff;
  mix-blend-mode: multiply;
}

.fx-0619-root.tmv-light .tmv-text {
  color: #000;
  background: #fff;
  mix-blend-mode: screen;
}

/* La vidéo joue : le dégradé de repli s’arrête ; vidéo en échec : elle est masquée, le dégradé remplit le texte */
.fx-0619-root.tmv-playing .tmv-stage::before {
  animation: none;
}

.fx-0619-root.tmv-failed .tmv-video {
  visibility: hidden;
}

@keyframes tmvFlow {
  to {
    background-position: 400% 50%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-0619-root .tmv-stage::before {
    animation: none;
  }
}
JavaScript (fx-0619)
(function () {
  var reduce = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
  function reduced() { return !!(reduce && reduce.matches); }

  // Clip de démonstration généré en local (aucun fichier) : retirez data-tmv-demo et donnez un src à la vidéo
  function demoClip(root, video) {
    var canvas = document.createElement('canvas');
    if (!canvas.captureStream) return null;
    canvas.width = 480;
    canvas.height = 270;
    var ctx = canvas.getContext('2d');
    var style = getComputedStyle(root);
    var colors = ['--tmv-c1', '--tmv-c2', '--tmv-c3', '--tmv-c4'].map(function (v, i) {
      return style.getPropertyValue(v).trim() || ['#6366f1', '#d946ef', '#06b6d4', '#f43f5e'][i];
    });
    var t = 0, last = 0, raf = 0;
    function draw() {
      var w = canvas.width, h = canvas.height;
      ctx.globalCompositeOperation = 'source-over';
      ctx.fillStyle = colors[0];
      ctx.fillRect(0, 0, w, h);
      for (var i = 1; i < 4; i++) {
        var x = w * (0.5 + 0.45 * Math.sin(t * (0.35 + i * 0.12) + i * 1.9));
        var y = h * (0.5 + 0.45 * Math.cos(t * (0.45 + i * 0.1) + i * 2.4));
        var g = ctx.createRadialGradient(x, y, 0, x, y, h * 0.95);
        g.addColorStop(0, colors[i]);
        g.addColorStop(1, 'rgba(0,0,0,0)');
        ctx.fillStyle = g;
        ctx.fillRect(0, 0, w, h);
      }
      ctx.globalCompositeOperation = 'lighter';
      for (var k = 0; k < 3; k++) {
        var bx = ((t * 70 + k * 190) % (w + 160)) - 80;
        var beam = ctx.createLinearGradient(bx - 50, 0, bx + 50, 0);
        beam.addColorStop(0, 'rgba(255,255,255,0)');
        beam.addColorStop(0.5, 'rgba(255,255,255,0.28)');
        beam.addColorStop(1, 'rgba(255,255,255,0)');
        ctx.fillStyle = beam;
        ctx.fillRect(bx - 50, 0, 100, h);
      }
    }
    function loop(now) {
      var dt = last ? Math.min(50, now - last) : 16;
      last = now;
      t += dt / 1000;
      draw();
      raf = requestAnimationFrame(loop);
    }
    draw();
    video.srcObject = canvas.captureStream(30);
    return {
      start: function () { if (!raf) { last = 0; raf = requestAnimationFrame(loop); } },
      stop: function () { cancelAnimationFrame(raf); raf = 0; },
      still: function () { draw(); }
    };
  }

  function init(root) {
    if (root.dataset.tmvReady) return;
    root.dataset.tmvReady = '1';
    var video = root.querySelector('.tmv-video');
    if (!video) return;
    var clip = null;
    if (video.hasAttribute('data-tmv-demo') && !video.getAttribute('src') && !video.querySelector('source')) {
      clip = demoClip(root, video);
    }
    var auto = video.autoplay;
    var visible = true, heldOff = false;
    // Classe tmv-playing dès qu’une image de la vidéo est affichée ; mouvement réduit = première image figée
    video.addEventListener('playing', function () {
      root.classList.add('tmv-playing');
      if (reduced()) video.pause();
    });
    video.addEventListener('play', function () { if (clip) clip.start(); });
    video.addEventListener('pause', function () { if (clip) clip.stop(); });
    video.addEventListener('emptied', function () { root.classList.remove('tmv-playing'); });
    // Aucune source lisible (fichier absent, format refusé) : classe tmv-failed, le texte garde le dégradé
    video.addEventListener('error', function () {
      setTimeout(function () {
        if (video.error || video.networkState === 3) {
          root.classList.remove('tmv-playing');
          root.classList.add('tmv-failed');
        }
      }, 0);
    }, true);
    video.addEventListener('canplay', function () {
      if (auto && visible && !heldOff && !reduced() && video.paused) {
        var c = video.play();
        if (c && c.catch) c.catch(function () {});
      }
    }, { once: true });

    function settle() {
      if (reduced()) {
        if (!root.classList.contains('tmv-playing')) {
          if (clip) clip.still();
          var p = video.play();
          if (p && p.catch) p.catch(function () {});
        } else {
          video.pause();
        }
      } else if (auto && visible) {
        var q = video.play();
        if (q && q.catch) q.catch(function () {});
      }
    }
    // Hors de l’écran, la vidéo se met en pause ; une vidéo en lecture automatique reprend au retour
    if ('IntersectionObserver' in window) {
      new IntersectionObserver(function (entries) {
        visible = entries[0].isIntersecting;
        if (!visible && !video.paused) { heldOff = auto; video.pause(); }
        else if (visible && heldOff) { heldOff = false; if (!reduced()) { var r = video.play(); if (r && r.catch) r.catch(function () {}); } }
      }).observe(root);
    }
    if (reduce && reduce.addEventListener) reduce.addEventListener('change', settle);
    settle();
  }

  function start() { document.querySelectorAll('.fx-0619-root').forEach(init); }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Votre vidéo (HTML — src ou <source> sur .tmv-video) clip de démonstration (data-tmv-demo) Retirez data-tmv-demo et donnez un fichier. Gardez muted, playsinline et loop ; sans autoplay, la vidéo attend un play() de votre part.
Taille du texte (CSS — --tmv-size) 4rem Plus les lettres sont grandes et épaisses, plus on voit la vidéo. clamp(2rem, 9vw, 6rem) suit la largeur de l'écran.
Fond et hauteur du bloc (CSS — --tmv-page, --tmv-height) #0a0a0f / 250px --tmv-page:transparent et --tmv-height:0px posent le titre directement sur votre page, qui doit être sombre (ou claire avec tmv-light).
Page claire (classe tmv-light sur la racine) absente Texte noir, modes de fusion inversés : le blanc autour des lettres disparaît au lieu du noir. Le fond par défaut passe en blanc.
Couleurs du repli et du clip (CSS — --tmv-c1 à --tmv-c4) #6366f1, #d946ef, #06b6d4, #f43f5e Teintes du dégradé visible pendant le chargement ou après un échec, et du clip de démonstration. Prenez les couleurs dominantes de votre vidéo pour un passage sans saut.
Élément du texte (HTML) <h2 class="tmv-text"> N'importe quel élément de type bloc convient (h1, p, div) : c'est la classe qui compte. Évitez un span seul, qui ne prend pas de hauteur.

FAQ

Comment mettre ma propre vidéo ?

Dans le HTML, sur la balise <video class="tmv-video"> : supprimez data-tmv-demo et ajoutez <source src="titre.webm" type="video/webm"> puis <source src="titre.mp4" type="video/mp4">. Un clip court (5 à 10 s), sans son, de 640 px de large suffit : on n'en voit que l'intérieur des lettres. Choisissez des images lumineuses et contrastées.

Que se passe-t-il si la vidéo ne se charge pas ?

Pendant le chargement, les lettres montrent le dégradé animé placé sous la vidéo. Si aucune source n'est lisible (fichier introuvable, format refusé), le script ajoute tmv-failed à la racine et masque la vidéo : le dégradé reste, le titre est lisible. Si le navigateur bloque la lecture automatique, la première image ou le dégradé s'affiche.

Pourquoi le texte doit-il être sur un fond sombre ou clair, pas gris ?

La vidéo est découpée par deux fusions de couleurs : le noir autour des lettres devient transparent sur la page (mode screen). Sur une page claire, la classe tmv-light fait l'inverse avec le blanc. Sur un fond moyen (gris, couleur vive), les lettres prennent un voile de cette couleur : le découpage reste juste, mais la vidéo paraît délavée.