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.

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; 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/> </div>
O próprio radar será composto por setores correspondentes aos aspectos da vida. Cada setor tem seu próprio índice.

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

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

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.

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

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"; 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); 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 => { 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); 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.

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 .
