Hola Estudio en el front-end y, en paralelo, en el proyecto de capacitación, desarrollo SPA en Vue.js para el back-end, que recopila datos del bot de búsqueda. El bot genera de 0 a 500 entradas, y tengo que: cargar, ordenar según los criterios especificados, mostrar en la tabla.
Ni el back-end ni el bot pueden ordenar datos, por lo que tengo que descargar todos los datos y procesarlos en el lado del navegador. La clasificación es muy rápida, pero la velocidad de descarga depende de la conexión, y los 500 registros indicados pueden cargar de 10 a 40 segundos.
Al principio, al cargar, mostré un spiner, cuyo inconveniente es que el usuario no sabe cuándo finalizará la descarga. En mi caso, la cantidad de registros que encontró el bot se conoce de antemano, por lo que puede mostrar cuántos% de los registros están cargados.
Para alegrar la espera del usuario, decidí mostrarle el proceso de carga:
- dígitos: cuántos% de registros ya están cargados
- horario - tiempo de carga de cada registro
- llenado -% de carga. Como el gráfico llena un bloque rectangular a medida que se carga, está claro qué parte del bloque queda por llenar
Aquí está la animación del resultado que estaba buscando y obtuve:

... en mi opinión, resultó divertido.
En el artículo te mostraré cómo avanzar hacia el resultado paso a paso. No dibujé gráficos de funciones en el navegador antes de la aldea, por lo que el desarrollo del indicador me trajo un conocimiento simple pero nuevo sobre el uso de SVG y Vue.
Elegir un lienzo o un método de renderizado SVG
Usé Canvas en un simple juego de serpientes en JS, y SVG, en un proyecto, simplemente lo inserté en la página en la etiqueta del objeto y noté que al escalar, las imágenes SVG siempre permanecían nítidas (por eso era un vector) y Canvas observó Desenfoque de imagen. Basado en esta observación, decidí dibujar un gráfico usando SVG, porque tienes que comenzar en algún momento.
Plan de trabajo
Basado en el marco Vue seleccionado, y el método seleccionado de formación de imágenes usando SVG, hice el siguiente plan de trabajo:
- Busque y estudie información sobre el tema del uso de SVG junto con Vue
- Experimentos con la formación y el cambio de SVG en el contexto de Vue
- Indicador de carga de prototipo
- Asignación del indicador de carga en un componente Vue separado
- Aplicación de componente en SPA
Empezando
Crear un proyecto en blancoTengo vue cli instalado . Para crear un nuevo proyecto, en el símbolo del sistema, ingreso vue create loadprogresser , selecciono la configuración del proyecto de forma predeterminada , se crea un nuevo proyecto vue con el nombre loadprogresser, luego elimino lo innecesario:
Busque y estudie información sobre el tema del uso de SVG con Vue
Gran sitio con información útil sobre HTML, CSS y SVG css.yoksel.ru Un buen ejemplo con SVG está disponible en la documentación del propio Vue. Ejemplo de gráfico SVG y dicho enlace . Basado en estos materiales, la plantilla de componente mínimo con SVG nació de la cual comienzo:
<template> <div class="wrapper"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> //svg // svg- </svg> </div> </template>
Experimentos con la formación y el cambio de SVG en el contexto de Vue
SVG rectángulo rect
rect - un rectángulo, la figura más simple. Creo svg con dimensiones 100x100px, y dibujo un rectángulo rectángulo con las coordenadas iniciales 25:25 y tamaños 50x50 px, el color de relleno predeterminado es negro (sin estilo)
Estilo SVG y seudoclase de desplazamiento:
Intentaré darle estilo al rectángulo rect en svg. Para hacer esto, agrego la clase "sample" a svg, en la sección de estilo del archivo vue agrego los estilos .sample rect (coloreo el rectángulo rect con amarillo) y .sample rect: hover que estiliza el elemento rect cuando se pasa sobre él:
Código fuente <template> <div id="app"> <svg class="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> <rect x=25 y=25 width="50px" height="50px"/> </svg> </div> </template> <script> export default { name: 'app' } </script> <style> .sample rect { fill: yellow; stroke: green; stroke-width: 4; transition: all 350ms; } .sample rect:hover { fill: gray; } </style>
Implementación de JSfiddle
Conclusión: svg encaja perfectamente en el archivo vue de plantilla y está diseñado con los estilos prescritos. ¡Se ha comenzado!
Ruta SVG como base del indicador
En esta sección, reemplazaré rect con ruta, <path :d="D" class="path"/>
en el atributo d de la etiqueta de ruta, pasaré la cadena D con las coordenadas de la ruta desde vue. La conexión se realiza mediante v-bind:d="D"
, que se abrevia como :d="D"
Línea D = “M 0 0 0 50 50 50 50 0 Z” dibuja tres líneas con coordenadas 0: 0-> 0: 50-> 50: 50-> 0:50 y cierra el contorno con el comando Z, formando un cuadrado de 50x50px a partir de coordenada 0: 0. Usando el estilo de ruta, la forma recibe un color de relleno amarillo y un borde gris de 1px.
Fuente de RUTA amarilla <template> <div id="app"> <svg class="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> <path :d="D" class="path"/> </svg> </div> </template> <script> export default { name: 'app', data(){ return { D:"M 0 0 0 50 50 50 50 0 Z" } } } </script> <style> .path { fill:yellow; stroke:gray; } </style>
Indicador de carga de prototipo
En la versión mínima, hice un diagrama simple. Un contenedor de svg con una altura de 100 px, un ancho de 400 px se inserta en la plantilla, se coloca una etiqueta de ruta dentro, al atributo d del cual agrego la cadena de ruta generada d a partir de los datos vue, que a su vez se forma a partir de la matriz timePoints donde se agrega uno de cada 400 (cada ancho del contenedor) un número aleatorio en el rango de 0 a 100. Todo es simple, en el enlace del ciclo de vida creado, se llama al método de actualización en el que se agregan nuevos puntos (aleatorios) al diagrama a través del método addTime, luego el método getSVGTimePoints devuelve una cadena para transmitir a PATH, a través de setTimeout reinicia el método de actualización
Más sobre la formación de cuerdas para PATH
La cadena para PATH se forma en el método getSVGTimePoints, a partir de la matriz timePoints que proceso con reduce. Como valor inicial de reducir, uso "M 0 0" (comienzo en la coordenada 0: 0). Además en reducción, se agregarán nuevos pares de coordenadas relativas dX y dY a la línea. La letra mayúscula "l" (la "L" grande indica las coordenadas absolutas) es responsable de que las coordenadas sean relativas, después de que d se coloca dX y luego dY, separadas por espacios. En este prototipo, dY = 1 (incremento de 1 px), en el futuro, a lo largo del eje X, me moveré con el incremento dX calculado a partir del ancho del contenedor y el número de puntos que deben colocarse en él. En la última línea de generación de RUTA
path +=`L ${this.timePoints.length} 0`
Forzosamente, desde el último punto, termino de construir la línea al eje X. Si necesita cerrar el contorno, puede agregar "Z" al final de la línea, al principio pensé que sin un contorno cerrado, la figura resultante no se llenaría (rellenaría), pero resultó ser incorrecto, donde no está cerrado, trazo - trazo no se dibujará.
getSVGTimePoints:function(){ let predY = 0 let path = this.timePoints.reduce((str, item)=>{ let dY = item - predY predY = item return str + `l 1 ${dY} ` },'M 0 0 ') path +=`L ${this.timePoints.length} 0`
Continuaré haciendo cambios. Mi indicador debe ajustarse en ancho y alto para que todos los puntos transmitidos quepan en el contenedor dado. Para hacer esto, recurra al DOM y descubra las dimensiones del contenedor
ref: obtener información sobre un elemento DOM
Al contenedor div (en el que se inserta svg) agrego una clase de contenedor para pasar el ancho y la altura a través de los estilos. Y para que svg ocupe todo el espacio del contenedor, establece su altura y ancho al 100%. RECT, a su vez, también ocupará todo el espacio del contenedor y será el fondo de PATH
<div id="app" class="wrapper" ref="loadprogresser"> <svg id="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> <rect x=0 y=0 width="100%" height="100%"/> <path :d="d" fill="transparent" stroke="black"/> </svg> </div>
Para encontrar mi contenedor DIV en el DOM virtual Vue, agrego el atributo ref y le doy un nombre con el que ref="loadprogresser"
. En el enlace de ciclo de vida mounted
, llamaré al método getScales (), en el que, con la cadena const {width, height} = this.$refs.loadprogresser.getBoundingClientRect()
encuentro el ancho y la altura del elemento DIV después de que aparezca en el DOM.
Además, cálculos simples del incremento a lo largo del eje X, dependiendo del ancho del contenedor y el número de puntos que deseamos encajar en él. La escala a lo largo del eje Y se recalcula cada vez que se encuentra el máximo en el valor transmitido.
transformar - cambiar el sistema de coordenadas
En esta etapa, noto que sería necesario cambiar el sistema de coordenadas para que la coordenada 0: 0 comience desde la esquina inferior izquierda y el eje Y crezca, no hacia abajo. Por supuesto, puede hacer cálculos para cada punto, pero SVG tiene un atributo de transformación que le permite transformar las coordenadas.
En mi caso, necesito aplicar una escala de -1 a las coordenadas Y (para que se establezcan los valores Y) y cambiar el origen a la altura del contenedor negativo . Como la altura del contenedor puede ser cualquiera (establecida mediante estilos), tuvimos que formar una línea de transformación de coordenadas en el gancho mounted
con el siguiente código: this.transform = `scale( 1, -1) translate(0,${-this.wrapHeight})`
Pero la transformación aplicada solo a PATH no funcionará, para esto necesita envolver PATH en un grupo (etiqueta g) al que se aplican las transformaciones de coordenadas:
<g :transform="transform"> <path :d="d" fill="transparent" stroke="black"/> </g>
Como resultado, las coordenadas se invirtieron correctamente, el indicador de descarga se acercó al diseño.
Texto SVG y centrado de texto
El texto es necesario para mostrar% de carga. La colocación del texto en el centro vertical y horizontalmente en SVG es bastante simple de organizar (en comparación con HTML / CSS), los atributos vienen al rescate (escribo inmediatamente los valores) dominante-baseline = "central" y text-anchor = "middle"
El texto en SVG se muestra con la etiqueta correspondiente:
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle">{{TextPrc}}</text>
donde TextPrc es el enlace a la variable correspondiente, calculada por una relación simple del número esperado de puntos a la cantidad transferida this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %`
this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %`
.
Las coordenadas del comienzo x = “50%” y = “50%” corresponden al centro del contenedor, y los atributos de línea de base dominante y ancla de texto son responsables de garantizar que el texto esté alineado vertical y horizontalmente.
Se han resuelto cosas básicas sobre el tema, ahora necesitamos seleccionar el prototipo del indicador en un componente separado.
Asignación del indicador de carga en un componente Vue separado
Para comenzar, decidiré sobre los datos que transferiré al componente, estos serán: maxSamples - el número de muestras en 100% de ancho, y Point - la unidad de datos (punto) que se ingresará en la matriz de puntos (en base a la cual, después del procesamiento, se formará horario). Los datos transmitidos al componente desde el padre, los coloco en la sección de accesorios
props:{ maxSamples: {
Problemas de reactividad
La propiedad calculada getPath es responsable del hecho de que se procesa el nuevo punto pasado al componente, que depende del Punto (y si lo hace, se vuelve a calcular cuando el Punto cambia)
Al principio hice un Punto de tipo Número, que es lógico, pero luego no se procesaron todos los puntos, sino solo diferentes de los anteriores. Por ejemplo, si solo el número 10 se transfiere del padre a dicho Punto, entonces solo se dibujará un punto en el gráfico, todos los siguientes serán ignorados ya que no difieren de los anteriores.
Reemplazar el tipo de Punto de Número con el objeto {valor: 0} condujo al resultado deseado: la propiedad calculada getPath () ahora procesa cada punto transmitido, a través de Point.value paso los valores de los puntos
Fuente del componente Progresser.vue <template> <div class="wrapper" ref="loadprogresser"> <svg class="wrapper__content" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" > <g :transform="transform"> <path :d="getPath"/> </g> <text x="50%" y="50%" dominant-baseline="central" text-anchor="middle"> {{TextPrc}} </text> </svg> </div> </template> <script> export default { props:{ maxSamples: {
Llamar desde el componente principal y pasar parámetros
Para trabajar con un componente, debe importarlo al componente principal
import Progresser from "./components/Progresser"
y declarar en la sección
components: {Progresser }
En la plantilla del componente principal, el componente indicador progresivo se inserta con la siguiente construcción:
<progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser>
A través de la clase "progresor", en primer lugar, se establecen los tamaños de bloque del indicador. Las muestras máximas (número máximo de puntos en el gráfico) de la variable principal SamplesInProgresser se transfieren al componente de accesorios, y el siguiente punto (como un objeto) de la variable Punto del objeto principal se pasa al punto de accesorios. El punto del padre se calcula en la función de actualización y representa números aleatorios crecientes. Me sale esta foto:

Fuente principal de App.vue <template> <div> <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> </div> </template> <script> import Progresser from "./components/Progresser" export default { name: 'app', data(){ return { SamplesInProgresser:400,// - Point:{value:0},//"" index:0, // - TimeM:100 // } }, created: function () { this.update() }, methods:{ update(){ if (this.index < this.SamplesInProgresser) { this.index++; this.Point = {value:(this.TimeM*Math.random() | 0)} this.TimeM *= 1.01 setTimeout(this.update, 0) } } }, components: { Progresser } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; margin-top: 60px; } /* */ .progresser { width: 300px; height: 80px; } </style>
Aplicación de componente en SPA
Llegar al punto en el que todo estaba a la altura. Y así, tengo operaciones asincrónicas para cargar registros sobre ciertas identidades de la base de datos. El tiempo de ejecución de una operación asincrónica no se conoce de antemano. Mediré el tiempo de ejecución de manera trivial, usando la nueva Fecha (). GetTime () antes y después de la operación, y transferiré la diferencia de tiempo resultante al componente. Naturalmente, el indicador se integrará en el bloque que aparecerá en la etapa de carga y ocultará la tabla para la que se cargan los datos.
async getCandidatesData(){ ... this.LoadRecords = true
En los datos del componente principal que prescribo con respecto a la indicación de carga:
data (){ return { ...
Y en la plantilla:
<div class="wait_loading" v-show="LoadRecords"> <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> </div>
Conclusiones
Como se predijo, nada complicado. Hasta cierto punto, puede tratar SVG como etiquetas HTML normales, con sus propios detalles. SVG es una herramienta poderosa que ahora usaré con más frecuencia en mi trabajo para la visualización de datos
Referencias
Descargar el código fuente del indicadorArtículo de ruta de SVG