Crear componentes de interfaz de usuario en SVG

Estoy completamente convencido de que SVG abre todo un mundo de creación de interfaces en la web. Puede parecer desalentador aprender SVG al principio, pero tienes una especificación que fue diseñada para crear formas y, sin embargo, todavía tiene elementos, como texto, enlaces y etiquetas de aria disponibles para ti. Puede lograr algunos de los mismos efectos en CSS, pero es un poco más particular lograr el posicionamiento correcto, especialmente en ventanas gráficas y para desarrollo responsivo.

Lo especial de SVG es que todo el posicionamiento se basa en un sistema de coordenadas, un poco como el juego Battleship . Eso significa que decidir dónde va todo y cómo se dibuja, así como cómo se relacionan entre sí, puede ser muy sencillo de razonar. El posicionamiento CSS es para el diseño, lo cual es excelente porque tienes cosas que se corresponden entre sí en términos del flujo del documento. Es más difícil trabajar con este rasgo positivo si estás creando un componente que es muy particular, con elementos superpuestos y colocados con precisión.

De hecho, una vez que aprendas SVG, podrás dibujar cualquier cosa y escalarla en cualquier dispositivo. Incluso este mismo sitio usa SVG para elementos de interfaz de usuario personalizados, como mi avatar, arriba (¡meta!).

Noemos todo sobre los SVG en esta publicación (puedes aprender algunos de esos fundamentos aquí, aquí, aquí y aquí), pero para ilustrar las posibilidades que SVG abre para el desarrollo de componentes de interfaz de usuario, hablemos de un uso particular. caso y analizar cómo pensaríamos en construir algo personalizado.

El componente de lista de tareas de la línea de tiempo.

Recientemente, estuve trabajando en un proyecto con mi equipo en Netlify. Queríamos mostrarle al espectador qué vídeo de una serie de vídeos de un curso estaba viendo en ese momento. En otras palabras, queríamos hacer algo que fuera como una lista de tareas pendientes, pero que mostrara el progreso general a medida que se completan los elementos. (Creamos una plataforma de aprendizaje gratuita con temática espacial y es genial. Sí, dije genial).

Así es como se ve:

Entonces, ¿cómo haríamos con esto? Mostraré un ejemplo tanto en Vue como en React para que puedas ver cómo podría funcionar en ambos marcos.

La versión Vue

Decidimos crear la plataforma en Next.js con finas pruebas internas (es decir, probar nuestro propio complemento de compilación Next en Netlify), pero hablo más con fluidez Vue, así que escribí el prototipo inicial en Vue y lo transfirí a React.

Aquí está la demostración completa de CodePen:

Repasemos un poco este código. En primer lugar, este es un componente de archivo único (SFC), por lo que la plantilla HTML, el script reactivo y los estilos con alcance están todos encapsulados en este único archivo.

Almacenaremos algunas tareas ficticias en data, incluidas si cada tarea se completa o no. También crearemos un método al que podamos llamar mediante una directiva de clic para que podamos alternar si el estado está terminado o no.

scriptexport default {  data() {    return {      tasks: [        {          name: 'thing',          done: false        },        // ...      ]    };  },  methods: {    selectThis(index) {      this.tasks[index].done = !this.tasks[index].done    }  }};/script

Ahora lo que queremos hacer es crear un SVG que tenga una flexibilidad viewBoxdependiendo de la cantidad de elementos. También queremos decirles a los lectores de pantalla que se trata de un elemento de presentación y que proporcionaremos un título con una identificación única de timeline. (Obtenga más información sobre cómo crear SVG accesibles).

template  div    div      svg :viewBox="`0 0 30 ${tasks.length * 50}`"                                  stroke="currentColor"            fill="white"           aria-labelledby="timeline"           role="presentation"           titletimeline element/title        !-- ... --      /svg    /div  /div/template

Está strokeconfigurado currentColorpara permitir cierta flexibilidad: si queremos reutilizar el componente en varios lugares, heredará lo que colorse usa en el encapsulador div.

A continuación, dentro del SVG, queremos crear una línea vertical que tenga la longitud de la lista de tareas. Las líneas son bastante sencillas. Tenemos valores x1y x2(donde la línea se traza en el eje x), y de manera similar, y1y y2.

line x1="10" x2="10" :y1="num2" :y2="tasks.length * num1 - num2" /

El eje x permanece consistentemente en 10 porque estamos dibujando una línea hacia abajo en lugar de de izquierda a derecha. Almacenaremos dos números en los datos: la cantidad que queremos que sea nuestro espacio, que será num1, y la cantidad que queremos que sea nuestro margen, que será num2.

data() {  return {    num1: 32,    num2: 15,    // ...  }}

El eje y comienza con num2, que se resta del final, así como del margen. Se tasks.lengthmultiplica por el espacio, que es num1.

Ahora necesitaremos los círculos que se encuentran en la línea. Cada círculo es un indicador de si una tarea se ha completado o no. Necesitaremos un círculo para cada tarea, por lo que lo usaremos v-forcon un único key, que es el índice (y es seguro usarlo aquí ya que nunca se reordenarán). Conectaremos la clickdirectiva con nuestro método y también pasaremos el índice como parámetro.

Los círculos en SVG se componen de tres atributos. El centro del círculo se traza en cxy cy,luego dibujamos un radio con r.Como la línea, cxcomienza en 10. El radio es 4 porque eso es lo que se puede leer en esta escala. cyEl espacio estará como la línea: índice multiplicado por el espacio ( num1), más el margen ( num2).

Finalmente, usaremos un ternario para configurar el archivo fill. Si la tarea está terminada, se completará con currentColor. De lo contrario, se completará con white(o cualquiera que sea el fondo). Esto podría completarse con un accesorio que pase en segundo plano, por ejemplo, donde tengas círculos claros y oscuros.

circle   @click="selectThis(i)"   v-for="(task, i) in tasks"  :key="task.name"  cx="10"  r="4"  :cy="i * num1 + num2"  :fill="task.done ? 'currentColor' : 'white'" /

Finalmente, usamos una cuadrícula CSS para alinear un div con los nombres de las tareas. Esto se presenta de la misma manera, donde recorreremos las tareas y también estamos vinculados a ese mismo evento de clic para alternar el estado terminado.

template  div    div       @click="selectThis(i)"      v-for="(task, i) in tasks"      :key="task.name"           {{ task.name }}    /div  /div/template

La versión reaccionar

Aquí es donde terminamos con la versión React. Estamos trabajando para abrir el código fuente de esto para que pueda ver el código completo y su historial. Aquí hay algunas modificaciones:

  • Estamos usando módulos CSS en lugar de SCF en Vue
  • Estamos importando el enlace de Next.js, de modo que en lugar de alternar un estado “listo”, llevamos al usuario a una página dinámica en Next.js.
  • Las tareas que estamos usando son en realidad etapas del curso (o “Misión”, como las llamamos) que se pasan aquí en lugar de ser realizadas por el componente.

La mayoría de las demás funciones son las mismas

import styles from './MissionTracker.module.css';import React, { useState } from 'react';import Link from 'next/link';function MissionTracker({ currentMission, currentStage, stages }) { const [tasks, setTasks] = useState([...stages]); const num1 = 32; const num2 = 15; const updateDoneTasks = (index) = () = {   let tasksCopy = [...tasks];   tasksCopy[index].done = !tasksCopy[index].done;   setTasks(tasksCopy); }; const taskTextStyles = (task) = {   const baseStyles = `${styles['tracker-select']} ${styles['task-label']}`;   if (currentStage === task.slug.current) {     return baseStyles + ` ${styles['is-current-task']}`;   } else {     return baseStyles;   } }; return (   div className={styles.container}     section       {tasks.map((task, index) = (         div           key={`mt-${task.slug}-${index}`}           className={taskTextStyles(task)}                    Link href={`/learn/${currentMission}/${task.slug.current}`}             {task.title}           /Link         /div       ))}     /section     section       svg         viewBox={`0 0 30 ${tasks.length * 50}`}         className={styles['tracker-svg']}                          stroke="currentColor"         fill="white"         aria-labelledby="timeline"         role="presentation"                titletimeline element/title         line x1="10" x2="10" y1={num2} y2={tasks.length * num1 - num2} /         {tasks.map((task, index) = (           circle             key={`mt-circle-${task.name}-${index}`}             onClick={updateDoneTasks(index)}             cx="10"             r="4"             cy={index * +num1 + +num2}             fill={               task.slug.current === currentStage ? 'currentColor' : 'black'             }             className={styles['tracker-select']}           /         ))}       /svg     /section   /div );}export default MissionTracker;

Versión definitiva

Puedes ver la versión final de trabajo aquí:

Ver sitio

Este componente es lo suficientemente flexible como para acomodar listas pequeñas y grandes, múltiples navegadores y tamaños adaptables. También permite al usuario comprender mejor dónde se encuentra en su progreso en el curso.

Pero este es sólo un componente. Puedes crear cualquier cantidad de elementos de la interfaz de usuario: perillas, controles, indicadores de progreso, cargadores… el cielo es el límite. Puede diseñarlos con CSS o estilos en línea, puede actualizarlos según los accesorios, el contexto, los datos reactivos, ¡el cielo es el límite! Espero que esto abra algunas puertas sobre cómo usted mismo puede desarrollar elementos de interfaz de usuario más atractivos para la web.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir