Cargar SVG dinámicamente

Y una función genérica para hacer llamadas con 'fetch'

JavGuerra,

SVG

Esta entrada en el blog complementa el contenido de los artículos Creación de un contador de resultados y Gráfica de lineas dinámica con SVG, pero puede leerse con independencia de ellos. Veremos cómo cargar un SVG (o cualquier otra cosa que nos propongamos) en el DOM una vez renderizada la página en el navegador apoyándonos en la instrucción fetch(). Se asume que el lector conoce el uso de esta instrucción.

La idea la tomé de este repositorio: Reproducción de acceso a SVG externo con CSS.

Como sabemos, podemos cargar imágenes, y también SVG en nuestra página con la etiqueta img:

<img src="imagen.svg" alt="Imagen de ejemplo en formato SVG" />

pero esto presenta un inconveniente. Desde el DOM, con JavaScript, no podemos acceder a las etiquetas del fichero SVG, y esto es fundamental para poder aplicar cambios dinámicos a la imagen.

La alternativa es poner el SVG en línea dentro del HTML, de una forma parecida a:

<svg id="imagen" version="1.1" xmlns="http://www.w3.org/2000/svg">
...
</svg>

En los puntos suspensivos estaría el cuerpo del SVG. Pero esto presentaría otro problema, y es que aumenta el tamaño del HTML de manera considerable, y lo hace menos legible.

La propuesta de esta entrada es la de hacer la carga del SVG dinámicamente, una vez se ha cargado la página, empleando fetch() para «traer» la imagen desde el servidor e insertarla en el DOM directamente tomando como referencia un elemento padre del HTML:

<div id="imagen"></div>

Veamos cómo de sencillo es esto.

El código

EL HTML de nuestra página sería pues muy simple:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ejemplo SVG</title>
    <script defer src="funciones.js"></script>
</head>
<body>

    <div id="imagen"></div>
    
</body>
</html>

Toda la magia se llevaría a cabo en la siguientes funciones para hacer posible la carga dinámica de la imagen:

01. function el(el) { return document.querySelector(el) }
02. 
03. function fetchAPI(direccion, _callback, tipo = 'json') {
04.     // setSpin(true);
05.     fetch(direccion)
06.         .then(respuesta => {
07.             if (!respuesta.ok) throw Error(respuesta.statusText);
08.             return tipo == 'json' ? respuesta.json() : respuesta.text();
09.         })
10.         .then(datos => _callback(datos))
11.         .catch(err => console.log(err));
12.         // .finally(setSpin(false));
13. }
14. 
15. let urlImagen = 'https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg';
16. let cargaImagen = xml => el('#imagen').innerHTML = xml;
17. fetchAPI(urlImagen, cargaImagen, 'text');

Nota: La línea 4 y la línea 12 llaman a la función setSpin() que describí en la entrada Spinner loader asíncrono, y podemos prescindir de ellas sin problema si no vamos a usarlas.

Veamos que hace esto. En la línea 1 tenemos una función cuya utilidad es la de acortar la llamada a querySelector(). Más allá de la estética, simplifica la escritura de código. La usaré en la línea 16.

La función fetchAPI() contiene la estructura genérica de una consulta fetch(), y la usaré en la línea 17, tras haber definido la dirección donde está ubicado el SVG en la línea 15 urlImagen, y la función que se ocupará de poner la imagen en el DOM de la línea 16 cargaImagen. Básicamente, la llamada:

fetchAPI(urlImagen, cargaImagen, 'texto');

lo que hace es pasar a fetch() la dirección de la imagen SVG y mediante la función callback cargaImagen(), que es una función flecha asignada a una variable, una vez fetch obtenga la imagen, ponerla dentro del contenedor div que describimos en el HTML. El parámetro 'texto' sirve para indicar que lo que vamos a cargar debe ser leído como texto, y no como un json. Realmente podríamos poner cualquier cosa aquí: ‘text’, ‘false’… lo que se nos ocurriese, ya que la función fechAPI consulta si lo que estamos indicando que debe devolver es ‘json’ en la línea 8:

    return tipo == 'json' ? respuesta.json() : respuesta.text();

y cualquier otra opción será entendida como que debe devolver la dos datos de tipo text() en vez de json(), que es el formato que requiere el contenido xml que describe el SVG que estamos cargando. por defecto, esta función devolverá un json si no indicamos este parámetro en la llamada.

En la línea 13 comprobamos que la petición fetch(direccion) no ha devuelto ningún error. fetch() no realiza esta comprobación por nosotros, y si se obtiene respuesta del servidor, así sea un error, entenderá que la petición se ha llevado a cabo, y devolverá la respuesta obtenida. Con esta línea nos aseguramos de que todo ha ido bien, si no es así, lanzo un error que será capturado en la línea 17 y mostrado por consola, parando la ejecución de la función.

Una vez devueltos los datos desde la línea 8, es en la línea 10 donde se procesa la función callback_ cargaImagen que paso como parámetro:

xml => el('#imagen').innerHTML = xml;

Esta función flecha toma los datos xml recibidos de fetch(), que deben estar en formato .text() y los inserta en el div que identificamos con el id="imagen" usando .innerHTML.

Desde ese momento ya disponemos de acceso completo al .SVG mediante el DOM.

Código completo

Puedes copiar y pegar este código para hacer pruebas:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ejemplo SVG</title>
</head>
<body>

    <div id="imagen"></div>

    <script>
        function el(el) { return document.querySelector(el) }

        function fetchAPI(direccion, _callback, tipo = 'json') {
            // setSpin(true);
            fetch(direccion)
                .then(respuesta => {
                    if (!respuesta.ok) throw Error(respuesta.statusText);
                    return tipo == 'json' ? respuesta.json() : respuesta.text();
                })
                .then(datos => _callback(datos))
                .catch(err => console.log(err));
                // .finally(setSpin(false));
        }

        let urlImagen = 'https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg';
        let cargaImagen = xml => el('#imagen').innerHTML = xml;
        fetchAPI(urlImagen, cargaImagen, 'text');
    </script>

</body>
</html>

Enlaces

Comentarios

← Volver