En esta ocasión he realizado Banner
en la que se muestra una imagen de fondo y texto transparente encima de la imagen. El cursor, con forma circular, también será transparente. Al hacer click
sobre la imagen, se produce animación con forma de circulo transparente que termina cubriendo toda la imagen. Al volver a hacer click, se produce la animación inversa del círculo. Mientras el Banner está "activo" se muestra vínculo en la zona inferior
Puede verse funcionando en este Pen. El soporte para Edge ha sido básico:
Código html del Banner
Se han creado cuatro banners
. Todo el código se ha creado mediante <svg>
ubicados dentro de < class="box">
.
Cada banner
tiene dos etiquetas SVG.
La primera etiqueta es para ubicar la imagen de fondo y sus máscaras. Máscaras para customizar el cursor y el texto principal.
La segunda etiqueta es para pintar el enlace del Banner cuando está activo.
El código del primero de los banners
es el siguiente:
<div class="box">
<svg viewbox="0 0 1000 600" class="mask" id="svg-mujer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<mask id="mask-mujer">
<g fill="white">
<rect width="100%" height="100%" fill="#555"/>
<circle class="mask__circle" cx="0" cy="0" r="60"/>
<circle class="mask__circle" cx="0" cy="0" r="60" fill="none" stroke= "#fff" stroke-width="1%">
<animate attributeType="SVG" attributeName="r" begin="0s" dur="1.5s" repeatCount="indefinite" from="1%" to="10%"/>
<animate attributeType="CSS" attributeName="stroke-width" begin="0s" dur="1.5s" repeatCount="indefinite" from="3%" to="0%"/>
<animate attributeType="CSS" attributeName="opacity" begin="0s" dur="1.5s" repeatCount="indefinite" from="1" to="0"/>
</circle>
<text x="50%" y="50%" class="mask__text" dominant-baseline="middle" text-anchor="middle">Mujer</text>
</g>
</mask>
</defs>
<image width="100%" class="mask__image" xlink:href="https://raw.githubusercontent.com/ivanalbizu/banner-con-animaciones-svg/master/img/mujer.jpg" style="mask: url(#mask-mujer)"/>
</svg>
<svg viewbox="0 0 1000 120" class="link" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<a href="#!">
<text x="50%" y="50%" class="link__text" dominant-baseline="middle" text-anchor="middle">Mujer</text>
</a>
</svg>
</div>
Código javascript del banner
Se han creado 5 funciones
const coord = el => {}
const handleMove = event => {}
const handleClick = event => {}
const openBox = (box) => {}
const closeBox = (box) => {}
Coordenadas del cursor dentro del Banner
La primera de las funciones const coord = el => {}
es una función de utilidad para ser usada en dos ocasiones. Sirve para devolver las coordenadas del cursor dentro de un elemento dado
const coord = el => {
let viewportX = el.clientX;
let viewportY = el.clientY;
let boxRectangle = el.target.getBoundingClientRect();
let localX = viewportX - boxRectangle.left;
let localY = viewportY - boxRectangle.top;
let x = (localX / boxRectangle.width) * 100;
let y = (localY / boxRectangle.height) * 100;
return {x,y}
}
Asignar posición del cursor a variables CSS
Con la función const handleMove = event => {}
se asigna el valor de la posición del cursor a variables CSS para ser usadas mediante CSS. Esta función está asignada a listener 'mousemove'
por lo que será constantemente actualizada cuando el cursor entre el alguno de los banners
const handleMove = event => {
let {x,y} = coord(event);
let root = document.documentElement;
root.style.setProperty('--x', `${x}%`);
root.style.setProperty('--y', `${y}%`);
}
Detectar evento Click en los Banners
Con const handleClick = event => {}
se detecta el evento click
sobre alguno de los banners
, se añade o quita la clase box--active
al <div class="box">
que contiene el banner
y según el caso, se ejecuta la función openBox(box);
o closeBox(box);
const handleClick = event => {
const target = event.target;
const box = target.closest('.box');
box.classList.toggle('box--active');
if (box.classList.contains('box--active')) {
openBox(box);
} else {
closeBox(box);
}
}
Abrir el Banner
En la función const openBox = (box) => {}
se crea un nuevo elemento SVG circle
y se añade al primer SVG. Se posiciona en la misma coordenada en la que se hizo click
mediante variables CSS y se le añade una clase CSS para transformar su radio mediante keyframes
. El efecto será nuevo círculo que terminará ocupando todo el banner
sin máscara.
const closeBox = (box) => {
const x = getComputedStyle(document.documentElement).getPropertyValue('--x');
const y = getComputedStyle(document.documentElement).getPropertyValue('--y');
const svgns = "http://www.w3.org/2000/svg";
let newCircle = document.createElementNS(svgns, 'circle');
newCircle.setAttributeNS(null, 'cx', x);
newCircle.setAttributeNS(null, 'cy', y);
newCircle.setAttributeNS(null, 'class', 'circle-click');
box.querySelector('g').appendChild(newCircle);
}
Cerrar el Banner
Con la función const closeBox = (box) => {}
se elimina el circle
anteriormente creado. Se añade clase CSS para la animación y se le da timeout
. Se necesita hacer comprobación para evitar posible conflicto de clicks
muy rápidos
const closeBox = (box) => {
let {x,y} = coord(event);
let root = document.documentElement;
root.style.setProperty('--x-close', `${x}%`);
root.style.setProperty('--y-close', `${y}%`);
const elClose = box.querySelector('.circle-click');
//Elimina el siguiente circle, en caso que fueran muy rápidos
//los clicks y no diera tiempo con setTimeout a eliminarlos
//Se usa nextSibling. Si se modifica DOM, habría que cambiar selector
if (elClose.nextSibling) elClose.nextSibling.remove();
elClose.classList.add('circle-click-closing');
elClose.style = `
cx: ${x};
cy: ${x};
r: 0
`;
setTimeout(() => {
elClose.remove();
}, 500);
}
Registrar listener para mousemove y click
Ahora toca, una vez el DOM esté cargado, inicializar la aplicación y registrar los listener
Se registran eventos mousemove
y click
sobre cada uno de los banners
También se añade código para que, caso de navegador Edge, se pueda ver una alternativa para este navegador. No le he dedicado tiempo. :)
document.addEventListener('DOMContentLoaded', () => {
let masks = document.querySelectorAll('.mask');
masks.forEach(mask => {
mask.addEventListener('mousemove', handleMove, false)
mask.addEventListener('click', handleClick, false)
});
if (/Edge\/\d./i.test(navigator.userAgent)){
let boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
box.style = `
background-image: url(${box.querySelector('image').getAttribute('xlink:href')});
background-size: 100%;
background-position: center;
cursor: pointer;
`;
});
}
});
Código css de los Banners
Lo más interesante a comentar son las variables css que son manipuladas con JS para ser usadas como coordendas cx
cy
de los elementos circle
del cursor y de la capa superpuesta sobre la imagen cuando el Banner se encuentra abierto
@import url('https://fonts.googleapis.com/css?family=Montserrat:700&display=swap');
:root {
--x: -60px;
--y: -60px;
--x-close: -60px;
--y-close: -60px;
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-color: #000;
}
svg {
width: 100%;
}
text {
font-family: 'Montserrat', sans-serif;
text-transform: uppercase;
}
.wrapper {
display: flex;
flex-wrap: wrap;
width: 100%;
background-color: #0c0c0c;
max-width: 90%;
margin: auto;
.box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
position: relative;
overflow: hidden;
}
}
.mask {
&__image {
cursor: none;
}
&__circle {
transform: translate(-120px);
}
&__text {
font-size: 120px;
text-shadow: 3px 3px 5px #2f2b2b;
}
&:hover {
.mask__circle {
transform: translate(var(--x), var(--y));
}
}
}
.link {
position: absolute;
bottom: 0;
background-color: rgba(0,0,0,.7);
width: 80%;
right: 0;
opacity: 0;
visibility: hidden;
transition: opacity .3s;
&__text {
font-size: 40px;
fill: #fff;
}
}
.box--active {
.mask__image {
cursor: default;
}
.link {
opacity: 1;
visibility: visible;
transform: translate(0, 0);
transition: opacity 1s ease-in-out .4s;
}
}
.circle-click {
animation-duration: 1.5s;
animation-fill-mode: forwards;
animation-name: circle-click;
pointer-events: none;
}
.circle-click-closing {
animation-duration: .5s;
animation-name: circle-click-closing;
}
@keyframes circle-click {
from {
r: 60;
}
to {
r: 145%;
}
}
@keyframes circle-click-closing {
from {
r: 145%;
}
to {
cx: var(--x-close);
cy: var(--y-close);
r: 0;
}
}
@media (min-width: 720px) {
.wrapper {
.box {
width: 50%;
}
}
}
@media (min-width: 1420px) {
.wrapper {
max-width: 76%;
}
}