Oi Estudo no front-end e, paralelamente, no projeto de treinamento, desenvolvo o SPA no Vue.js para o back-end, que coleta dados do bot de pesquisa. O bot gera de 0 a 500 entradas, e eu tenho que: fazer upload, classificar pelos critérios especificados, mostrar na tabela.
Nem o back-end nem o bot podem classificar dados, então eu tenho que baixar todos os dados e processá-los no lado do navegador. A classificação é muito rápida, mas a velocidade do download depende da conexão e os 500 registros indicados podem ser carregados de 10 a 40 segundos.
No início, ao carregar, mostrei um spiner, cuja desvantagem é que o usuário não sabe quando o download será encerrado. No meu caso, o número de registros que o bot encontrado é conhecido antecipadamente, para que você possa mostrar quantos% de registros são carregados.
Para alegrar a espera do usuário, decidi mostrar a ele o processo de carregamento:
- dígitos - quantos% de registros já estão carregados
- agenda - tempo de carregamento de cada registro
- enchimento -% de carga. Como o gráfico preenche um bloco retangular enquanto ele carrega, fica claro qual parte do bloco ainda precisa ser preenchida
Aqui está a animação do resultado que eu estava buscando e obtive:

... na minha opinião, ficou engraçado.
No artigo, mostrarei como avançar passo a passo em direção ao resultado. Como não desenhei gráficos de funções no navegador antes da vila, o desenvolvimento do indicador me trouxe conhecimento simples, mas novo, sobre o uso do SVG e do Vue.
Escolhendo um método de renderização Canvas ou SVG
Usei o Canvas em um jogo simples de cobra em JS e SVG, em um projeto, apenas o inseri na página com a tag object e notei que, ao escalar, as imagens SVG sempre mantinham nitidez (é por isso que era um vetor) e o Canvas observava desfocar a imagem. Com base nessa observação, decidi desenhar um gráfico usando SVG, porque você precisa começar um dia.
Plano de trabalho
Com base na estrutura selecionada do Vue e no método selecionado de formação de imagem usando SVG, fiz o seguinte plano de trabalho:
- Pesquise e estude informações sobre o tópico do uso do SVG em conjunto com o Vue
- Experimentos com a formação e mudança de SVG no contexto de Vue
- Indicador de carregamento do protótipo
- Alocação do indicador de carregamento em um componente Vue separado
- Aplicação de componente no SPA
Introdução
Criando um projeto em brancoEu tenho o vue cli instalado . Para criar um novo projeto, no prompt de comando, insiro vue create loadprogresser , selecione as configurações do projeto por padrão , um novo projeto vue é criado com o nome loadprogresser e removo o desnecessário:
Pesquise e estude informações sobre o tópico do uso do SVG em conjunto com o Vue
Ótimo site com informações úteis sobre HTML, CSS e SVG css.yoksel.ru Um bom exemplo do SVG está disponível na documentação do próprio Vue: Exemplo do SVG-graph e um link . Com base nesses materiais, nasceu o modelo de componente mínimo com SVG a partir do qual começo:
<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 com a formação e mudança de SVG no contexto de Vue
Retângulo SVG ret
rect - um retângulo, a figura mais simples. Eu crio svg com dimensões 100x100px e desenho um retângulo com as coordenadas iniciais 25:25 e tamanhos 50x50 px, a cor de preenchimento padrão é preta (sem estilo)
Estilo SVG e pseudo-classe suspensa:
Vou tentar estilizar o retângulo em svg. Para fazer isso, adiciono a classe "sample" ao svg, na seção style do arquivo vue adiciono os estilos .sample rect (coro o retângulo com amarelo) e .sample rect: hover que estiliza o elemento rect quando você passa o mouse sobre ele:
Código fonte <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>
Implementação JSfiddle
Conclusão: svg se encaixa perfeitamente no arquivo vue do modelo e é estilizado com os estilos prescritos. Um começo foi feito!
Caminho SVG como base do indicador
Nesta seção, substituirei rect por path, <path :d="D" class="path"/>
no atributo d da tag path, passarei a string D com as coordenadas do path do vue. A conexão é feita através do v-bind:d="D"
, que é abreviado como :d="D"
A linha D = "M 0 0 0 50 50 50 50 0 Z" desenha três linhas com as coordenadas 0: 0-> 0: 50-> 50: 50-> 0:50 e fecha o contorno com o comando Z, formando um quadrado de 50x50px a partir de coordenada 0: 0. Usando o estilo do caminho, a forma recebe uma cor de preenchimento amarelo e uma borda cinza de 1px.
Fonte amarela do CAMINHO <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 carregamento do protótipo
Na versão mínima, fiz um diagrama simples. Um contêiner svg com uma altura de 100px, uma largura de 400px é inserida no modelo, uma tag de caminho é inserida no interior, ao atributo d ao qual adiciono a string de caminho gerada d a partir dos dados do vue, que por sua vez é formado a partir da matriz timePoints, em que, a cada 10 ms, um dos 400 é adicionado (por a largura do contêiner) um número aleatório no intervalo de 0 a 100. Tudo é simples, no gancho do ciclo de vida criado, o método de atualização é chamado no qual novos pontos (aleatórios) são adicionados ao diagrama por meio do método addTime e, em seguida, o método getSVGTimePoints retorna uma sequência de caracteres para passar para PATH, por meio de setTimeout reinicia o método de atualização
Mais sobre formação de strings para PATH
A cadeia de caracteres para PATH é formada no método getSVGTimePoints, a partir da matriz timePoints que eu processo com reduzir. Como valor inicial de redução, eu uso "M 0 0" (comece na coordenada 0: 0). Além disso, na redução, novos pares de coordenadas relativas dX e dY serão adicionados à linha. A letra maiúscula “l” é responsável pelas coordenadas serem relativas (o “L” grande indica as coordenadas absolutas), depois que “l” é colocado dX e depois dY, separados por espaços. Neste protótipo, dY = 1 (incremento de 1px), no futuro, ao longo do eixo X, moverei-me com o incremento dX calculado a partir da largura do contêiner e o número de pontos que precisam ser colocados nele. Na última linha da geração PATH
path +=`L ${this.timePoints.length} 0`
Forço, a partir do último ponto, termino de construir a linha no eixo X. Se você precisar fechar o contorno, poderá adicionar "Z" ao final da linha, primeiro pensei que sem um contorno fechado, a figura resultante não seria preenchida (preenchimento), mas isso estava errado, onde não estiver fechado, o golpe - o golpe não será desenhado.
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`
Vou continuar a fazer alterações. Meu indicador deve ser dimensionado em largura e altura para que todos os pontos transmitidos se ajustem ao contêiner especificado. Para fazer isso, vá para o DOM e descubra as dimensões do contêiner
ref - obtendo informações sobre um elemento DOM
No contêiner div (no qual o svg é inserido), adiciono uma classe de wrapper para passar a largura e a altura pelos estilos. E para que o svg ocupe todo o espaço do contêiner, defina sua altura e largura para 100%. O RECT, por sua vez, também ocupará todo o espaço do contêiner e será o pano de fundo para o 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 meu contêiner DIV no DOM Vue virtual, adiciono o atributo ref e ref="loadprogresser"
nome pelo qual procurarei ref="loadprogresser"
. No gancho do ciclo de vida mounted
, chamarei o método getScales (), no qual, com a sequência const {width, height} = this.$refs.loadprogresser.getBoundingClientRect()
a largura e a altura do elemento DIV depois que ele aparece no DOM.
Além disso, cálculos simples do incremento ao longo do eixo X, dependendo da largura do contêiner e do número de pontos que queremos ajustar nele. A escala ao longo do eixo Y é recalculada cada vez que o máximo é encontrado no valor transmitido.
transformar - alterar o sistema de coordenadas
Nesse ponto, percebo que precisamos alterar o sistema de coordenadas para que a coordenada 0: 0 comece no canto inferior esquerdo e o eixo Y cresça para cima e não para baixo. Obviamente, é possível fazer cálculos para cada ponto, mas o SVG possui um atributo de transformação que permite transformar coordenadas.
No meu caso, preciso aplicar uma escala de -1 às coordenadas Y (para que os valores Y sejam recuperados) e mudar a origem para a altura negativa do contêiner. Como a altura do contêiner pode ser qualquer (especificada por meio de estilos), tivemos que formar uma linha de transformação de coordenadas no gancho mounted
com o seguinte código: this.transform = `scale( 1, -1) translate(0,${-this.wrapHeight})`
Mas a transformação aplicada somente ao PATH não funcionará, para isso, é necessário agrupar o PATH em um grupo (tag g) ao qual as transformações de coordenadas são aplicadas:
<g :transform="transform"> <path :d="d" fill="transparent" stroke="black"/> </g>
Como resultado, as coordenadas foram revertidas corretamente, o indicador de download ficou mais próximo do design
Texto SVG e centralização de texto
O texto é necessário para exibir% de carga. A colocação do texto no centro vertical e horizontalmente no SVG é bastante simples de organizar (em comparação com HTML / CSS), os atributos são resgatados (eu imediatamente defino os valores) dominante-linha de base = "central" e texto-âncora = "meio"
O texto em SVG é exibido com a tag correspondente:
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle">{{TextPrc}}</text>
onde TextPrc é a ligação à variável correspondente, calculada por uma simples proporção do número esperado de pontos em relação à quantidade transferida this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %`
this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %`
.
As coordenadas do início x = "50%" y = "50%" correspondem ao centro do contêiner, e os atributos de linha de base dominante e âncora de texto são responsáveis por garantir que o texto esteja alinhado vertical e horizontalmente.
Coisas básicas sobre o tópico foram resolvidas, agora precisamos selecionar o protótipo do indicador em um componente separado.
Alocação do indicador de carregamento em um componente Vue separado
Para começar, determinarei os dados que transferirei para o componente: maxSamples - o número de amostras com 100% de largura e Point - a unidade de dados (ponto) que será inserida na matriz de pontos (com base na qual, após o processamento, eles serão formados horário). Os dados transmitidos ao componente pelo pai, coloco na seção adereços
props:{ maxSamples: {
Problemas de reatividade
A propriedade computada getPath é responsável pelo fato de que o novo ponto passado para o componente é processado, o que depende do Point (e, se o fizer, será recalculado quando o Point mudar)
No começo, criei um Point do tipo Number, que é lógico, mas nem todos os pontos foram processados, mas apenas diferentes dos anteriores. Por exemplo, se apenas o número 10 for transferido do pai para esse ponto, apenas um ponto será desenhado no gráfico, todos os subsequentes serão ignorados, pois não diferem dos anteriores.
Substituir o tipo de ponto de Number pelo objeto {value: 0} levou ao resultado desejado - a propriedade calculada getPath () agora processa cada ponto transmitido, através de Point.value, passo os valores dos pontos
Origem do 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: {
Chamando a partir do componente pai e passando parâmetros
Para trabalhar com um componente, você precisa importá-lo para o componente pai
import Progresser from "./components/Progresser"
e declarar na seção
components: {Progresser }
No modelo do componente pai, o componente indicador progressivo é inserido com a seguinte construção:
<progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser>
Através da classe "progreser", primeiro, os tamanhos de bloco do indicador são definidos. MaxSamples (número máximo de pontos no gráfico) da variável pai SamplesInProgresser são transferidos para os props componentes, e o próximo ponto (na forma de um objeto) da variável Point do objeto pai é transferido para props Point. O ponto dos pais é calculado na função de atualização e representa números aleatórios crescentes. Eu recebo esta imagem:

Origem pai 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>
Aplicação de componente no SPA
Chegando ao ponto em que tudo estava fazendo. E, portanto, tenho operações assíncronas para carregar registros sobre determinadas identidades do banco de dados. O tempo de execução de uma operação assíncrona não é conhecido antecipadamente. Medirei o tempo de execução de uma maneira trivial, usando o novo Date (). GetTime () antes e após a operação, e transferirei a diferença de tempo resultante para o componente. Naturalmente, o indicador será incorporado no bloco que aparecerá no estágio de carregamento e ocultará a tabela na qual os dados estão sendo carregados.
async getCandidatesData(){ ... this.LoadRecords = true
Nos dados do componente pai, prescrevo em relação à indicação de carga:
data (){ return { ...
E no modelo:
<div class="wait_loading" v-show="LoadRecords"> <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> </div>
Conclusões
Como previsto, nada complicado. Até algum momento, você pode tratar o SVG como tags HTML regulares, com suas próprias especificidades. O SVG é uma ferramenta poderosa que agora usarei com mais frequência no meu trabalho para visualização de dados
Referências
Código-fonte do indicador de downloadArtigo Svg-path