Vida cheia no Svelte

Radislav Gandapas tem um excelente livro Complete J. Ele fala sobre como avaliar a direção de sua vida e como desenvolver um plano de desenvolvimento.


Eu queria criar uma ferramenta que estará no meu smartphone e ajude a compor meu radar.


imagem


1. Preparação


O código fonte do tutorial e da demonstração pode ser visto aqui .


Como este projeto é pequeno, escreveremos imediatamente no REPL , o editor online mais sofisticado. Se você gosta de desenvolvimento local, pode usar modelos de webpack ou rollup svelte.


Posso recomendar a ferramenta codesandbox online como uma alternativa ao desenvolvimento local.


Se você usa o VScode, recomendo instalar o plugin svelte-vscode


Então, abrimos o REPL e começamos


2. Moldura


Agora temos o arquivo App.svelte , este é o ponto de entrada para o aplicativo. Os componentes Svelte são estilizados na tag style , como no html comum. Ao fazer isso, você obtém isolamento de estilo no nível do componente. Se você precisar adicionar estilos globais que estarão acessíveis "fora" do objeto, use a diretiva : global () . Adicione estilos e crie um contêiner para nosso aplicativo.


App.svelte
<style> :global(body) { height: 100%; overscroll-behavior: none; /*  pull to refresh*/ user-select: none; /*      */ margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; background: rgb(35, 41, 37); } :global(html) { height: 100%; } .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: center; height: 100%; } </style> <div class="container"> </div> 

Crie o componente Radar.svelte . Este será o elemento SVG no qual desenharemos nossa roda.


Radar.svelte
 <svg viewBox="-115 -110 230 220"> </svg> 

O código Javascript no componente Svelte é colocado na tag de script . Importamos nosso Radar.svelte para o App.svelte e o desenhamos.


App.svelte
 <script> import Radar from './Radar.svelte' /*    */ </script> <style> :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; background: rgb(35, 41, 37); } :global(html) { height: 100%; } .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: center; height: 100%; } </style> <div class="container"> <Radar/> <!--   Radar --> </div> 

O próprio radar será composto por setores correspondentes aos aspectos da vida. Cada setor tem seu próprio índice.


imagem


Cada setor consiste em uma grade que, por sua vez, é um setor com um tamanho menor.


imagem


Para desenhar um setor, precisamos conhecer as coordenadas dos três vértices.


imagem


O vértice A está sempre com as coordenadas [0, 0], pois a origem estará no centro do nosso radar. Para encontrar os vértices B e C, usamos uma função de um excelente tutorial sobre grades hexagonais. Na entrada, a função recebe o tamanho e a direção do setor e retorna uma string com as coordenadas 'x, y'.
Crie um arquivo getHexCorner.js , onde colocamos nossa função getHexCorner (tamanho, direção)


getHexCorner.js
 export default function getHexCorner(size, direction) { const angleDeg = 60 * direction - 30; const angleRad = (Math.PI / 180) * angleDeg; return `${size * Math.cos(angleRad)},${size * Math.sin(angleRad)}`; } 

Agora crie o componente de setor Sector.svelte , que desenha a grade. Precisamos de um ciclo de 10 etapas. No corpo do componente, o svelte não é capaz de implementar o loop for, então acabei de criar uma matriz de grade, a qual repetirei na diretiva #each . Se você tiver alguma idéia de como torná-lo mais elegante, escreva sobre isso nos comentários.


Sector.svelte
 <script> import getHexCorner from "./getHexCorner.js"; export let direction = 0; const grid = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; </script> <style> polygon { fill: #293038; stroke: #424a54; } </style> {#each grid as gridValue, i} <polygon points={`${getHexCorner(gridValue * 10, direction)}, ${getHexCorner(gridValue * 10, direction + 1)}, 0, 0`} strokeLinejoin="miter-clip" stroke-dasharray="4" stroke-width="0.5" /> {/each} 

Importe e desenhe um setor no componente Radar.svelte .


Radar.svelte
 <script> import Sector from './Sector.svelte'; </script> <svg viewBox="-115 -110 230 220"> <Sector/> </svg> 

Agora, nosso aplicativo exibe 1 setor.


imagem


3. armazenamento de dados


Para desenhar o radar inteiro, você precisa conhecer a lista de setores. Portanto, criaremos um armazenamento de estado. Usaremos um portão personalizado no qual implementamos a lógica de atualização do estado. Em geral, este é o repositório Svelte usual, que é envolvido em uma função. Isso protege o repositório de alterações, fornecendo um conjunto de ações disponíveis. Eu gosto dessa abordagem, pois a estrutura de dados e a lógica de trabalhar com eles estão em um só lugar.


Crie um arquivo store.js


Vamos precisar de dois armazenamentos:


  • radar para armazenar valores atuais
  • activeSector para armazenar o setor ativo se ocorrerem eventos touchmove e mousemove.

store.js
 import { writable } from "svelte/store"; const defaultStore = ["hobby", "friendship", "health", "job", "love", "rich"]; function Radar() { /*      */ const { subscribe, update } = writable(defaultStore.map(item=>({name:item, value:0}))); /*         */ return { subscribe, set: (id, value) => update(store => store.map(item => (item.name === id ? { ...item, value } : item)) ) }; } export const radar = Radar(); export const activeSector = writable(null); 

Agora importamos o armazenamento criado para o componente Radar.svelte e adicionamos a lógica para renderizar o radar completo.


Radar.svelte
 <script> import { radar } from "./store.js"; import Sector from "./Sector.svelte"; </script> <svg viewBox="-115 -110 230 220"> {#each $radar as sector, direction (sector.name)} <Sector {...sector} {direction} /> {/each} </svg> 

Algumas sutilezas da diretiva #each . Usamos o nome da variável $ radar . A diretiva $ deixa claro para o compilador Svelte que nossa expressão é um repositório e cria uma assinatura para alterações. A variável direction armazena o índice da iteração atual, conforme definiremos a direção do nosso setor. A expressão (nome do setor) aponta para o id do objeto na iteração . Tecla analógica em React.


Agora nossa grade fica assim


imagem


Resta preparar o setor para trabalhar com eventos de clicar e arrastar.
O evento touchmove, diferente do mousemove, é acionado apenas no elemento em que foi iniciado. Portanto, não podemos capturar o momento em que o ponteiro se mudou para outro setor. Para resolver esse problema, na marcação do elemento, armazenaremos o nome atual do setor e seu valor. No momento do evento, determinaremos qual setor está sob o cursor e alteraremos seu valor.

Observe que o Svelte pode expandir a construção {varName} em varName = {varName} . Isso simplifica bastante as propriedades prokidyvanie.


Sector.svelte
 <script> import getHexCorner from "./getHexCorner.js"; export let direction = 0; export let name; export let value; const grid = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; </script> <style> polygon { fill: #293038; stroke: #424a54; } .rich { fill: #469573; } .hobby { fill: #7c3f7a; } .friendship { fill: #5c6bc0; } .health { fill: #e5b744; } .job { fill: #e16838; } .love { fill: #e23f45; } </style> {#each grid as gridValue, i} <polygon points={`${getHexCorner(gridValue * 10, direction)}, ${getHexCorner(gridValue * 10, direction + 1)}, 0, 0`} strokeLinejoin="miter-clip" stroke-dasharray="4" stroke-width="0.5" class={value >= gridValue ? name : ''} {name} value={gridValue} /> /> {/each} 

Se adicionarmos um valor diferente de zero em nossa loja (store.js), isso resultará:


4. Eventos


É hora de dar vida ao nosso radar, criar um manipulador que pegue um nó na entrada e dentro dele processe os eventos de toque e mouse.


handleRadar.js
 import { radar, activeSector } from "./store.js"; /*  get            */ import { get } from "svelte/store"; export default function handleRadar(node) { const getRadarElementAtPoint = e => { /*   :    */ const event = e.touches ? e.touches[0] : e; const element = document.elementFromPoint(event.pageX, event.pageY); /*       html  */ const score = element.getAttribute("value"); const id = element.getAttribute("name"); return { id, score, type: event.type }; }; const start = e => { /*       */ const { id } = getRadarElementAtPoint(e); /*     */ activeSector.set(id); }; const end = () => { /*    */ activeSector.set(null); }; const move = e => { /*   requestAnimationFrame       */ window.requestAnimationFrame(() => { const { id, score, type } = getRadarElementAtPoint(e); /* ,      , ..    ,     */ if (!id || (id !== get(activeSector) && type !== "click") || !score) return; /*    */ radar.set(id, score); }); }; /*   */ node.addEventListener("mousedown", start); node.addEventListener("touchstart", start); node.addEventListener("mouseup", end); node.addEventListener("touchend", end); node.addEventListener("mousemove", move); node.addEventListener("touchmove", move); node.addEventListener("touch", move); node.addEventListener("click", move); /*     destroy,          DOM */ return { destroy() { node.removeEventListener("mousedown", start); node.removeEventListener("touchstart", start); node.removeEventListener("mouseup", end); node.removeEventListener("touchend", end); node.removeEventListener("mousemove", move); node.removeEventListener("touchmove", move); node.removeEventListener("touch", move); node.removeEventListener("click", move); } }; } 

Agora basta adicionar nosso manipulador ao elemento de radar svg através da diretiva use:


Radar.svelte
 <script> import { radar } from "./store.js"; import Sector from "./Sector.svelte"; import handleRadar from "./handleRadar.js"; </script> <svg viewBox="-115 -110 230 220" use:handleRadar> {#each $radar as sector, direction (sector.name)} <Sector {...sector} {direction} /> {/each} </svg> 

O radar agora responde a cliques e arrastamentos.


6. Toques finais


Adicionar legendas para setores e descrição


Sector.svelte
 <script> import getHexCorner from "./getHexCorner.js"; export let name; export let value; export let direction; const grid = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; const flip = direction === 2 || direction === 1; const radarTranslation = { hobby: "", friendship: "", health: "", job: "", love: "", rich: "" }; </script> <style> polygon { fill: #293038; stroke: #424a54; } text { font-size: 8px; fill: white; } .value { font-weight: bold; font-size: 12px; } .rich { fill: #469573; } .hobby { fill: #7c3f7a; } .friendship { fill: #5c6bc0; } .health { fill: #e5b744; } .job { fill: #e16838; } .love { fill: #e23f45; } </style> {#each grid as gridValue, i} <polygon points={`${getHexCorner(gridValue * 10, direction)}, ${getHexCorner(gridValue * 10, direction + 1)}, 0, 0`} strokeLinejoin="miter-clip" stroke-dasharray="4" stroke-width="0.5" class={value >= gridValue ? name : ''} {name} value={gridValue} /> {/each} <g transform={`translate(${getHexCorner(105, flip ? direction + 1 : direction)}) rotate(${direction * 60 + (flip ? -90 : 90)})`}> <text x="50" y={flip ? 5 : 0} text-anchor="middle"> {radarTranslation[name]} </text> <text x="50" y={flip ? 18 : -10} text-anchor="middle" class="value"> {value} </text> </g> 

O radar deve ficar assim.


imagem


5. Bônus


Expandi um pouco a funcionalidade do radar, adicionei armazenamento de dados no localStorage e um plano de ação. Você pode experimentar o aplicativo de verificação da vida , o código fonte está disponível no gitlab .


Source: https://habr.com/ru/post/pt458974/


All Articles