DJ · house, disco, edits — basé à Lyon
VELMORAY
Prochaines dates
Clubs, festivals, soirées privées. Réponse sous 48 h.
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.
Mis à jour le
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 →
DJ · house, disco, edits — basé à Lyon
Prochaines dates
Clubs, festivals, soirées privées. Réponse sous 48 h.
Grimolte
12,4 k abonnés · jeux de plateforme
EN DIRECT
1 284 spectateurs · discussion ouverte
Survolez ou sélectionnez une affiche : la bande-annonce passe dans son titre.
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.
<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.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.<button> qui appelle pause() sur la vidéo.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.
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.
Copiez les trois blocs dans votre page. Aucune dépendance.
<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>
.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;
}
}
(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();
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
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. |
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.
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.
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.