Pleine vie sur Svelte

Radislav Gandapas a un excellent livre Complete J. Il explique comment évaluer l'orientation de votre vie et comment élaborer un plan de développement.


Je voulais créer un outil qui sera dans mon smartphone et aider à composer mon radar.


image


1. Préparation


Le code source du tutoriel et de la démo peut être consulté ici .


Ce projet est petit, nous allons donc écrire tout de suite dans REPL , l'éditeur svelte en ligne. Si vous aimez le développement local, vous pouvez utiliser des modèles webpack ou rollup svelte.


Je peux recommander l'outil codesandbox en ligne comme alternative au développement local.


Si vous utilisez VScode, je vous recommande d'installer le plugin svelte-vscode


Donc, nous ouvrons REPL et nous commençons


2. Cadre


Nous avons maintenant le fichier App.svelte , c'est le point d'entrée de l'application. Les composants Svelte sont stylisés dans la balise style , comme en html standard. Ce faisant, vous obtenez une isolation de style au niveau du composant. Si vous devez ajouter des styles globaux qui seront disponibles "en dehors" de l'objet, vous devez utiliser la directive : global () . Ajoutez des styles et créez un conteneur pour notre application.


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> 

Créez le composant Radar.svelte . Ce sera l'élément SVG dans lequel nous allons tirer notre roue.


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

Le code Javascript dans le composant Svelte est placé dans la balise de script . Nous importons notre Radar.svelte dans App.svelte et le dessinons .


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> 

Le radar lui-même sera composé de secteurs correspondant aux aspects de la vie. Chaque secteur a son propre indice.


image


Chaque secteur est constitué d'une grille qui, à son tour, est un secteur de plus petite taille.


image


Pour dessiner un secteur, nous devons connaître les coordonnées des trois sommets.


image


Le sommet A est toujours avec les coordonnées [0, 0], puisque l'origine sera au centre de notre radar. Pour trouver les sommets B et C, nous utilisons une fonction d' un excellent tutoriel sur les grilles hexagonales. En entrée, la fonction reçoit la taille et la direction du secteur et renvoie une chaîne avec les coordonnées «x, y».
Créez un fichier getHexCorner.js , où nous mettons notre fonction getHexCorner (taille, direction)


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)}`; } 

Créez maintenant le composant sectoriel Sector.svelte , qui dessine la grille. Nous avons besoin d'un cycle en 10 étapes. Dans le corps du composant, svelte n'est pas en mesure d'implémenter la boucle for, donc je viens de créer un tableau de grille, que je vais répéter dans la directive #each . Si vous avez des idées pour le rendre plus élégant, écrivez-le dans les commentaires.


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} 

Importez et dessinez un secteur dans le composant Radar.svelte .


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

Maintenant, notre application affiche 1 secteur.


image


3. Stockage des données


Pour dessiner l'ensemble du radar, vous devez connaître la liste des secteurs. Par conséquent, nous allons créer un magasin d'état. Nous utiliserons une porte personnalisée dans laquelle nous implémenterons la logique de mise à jour de l'état. En général, il s'agit du référentiel Svelte habituel, qui est enveloppé dans une fonction. Cela protège le référentiel des modifications en fournissant un ensemble d'actions disponibles. J'aime cette approche dans la mesure où la structure des données et la logique de travailler avec elles sont en un seul endroit.


Créer un fichier store.js


Nous aurons besoin de deux stockages:


  • radar pour stocker les valeurs actuelles
  • activeSector pour stocker le secteur actif si des événements touchmove et mousemove se produisent.

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); 

Maintenant, nous importons le stockage créé dans le composant Radar.svelte et ajoutons la logique de rendu du radar complet.


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> 

Quelques subtilités de la directive #each . Nous utilisons le nom de variable $ radar . La directive $ indique clairement au compilateur Svelte que notre expression est un référentiel et crée un abonnement pour les modifications. La variable direction stocke l'indice de l'itération actuelle, par lequel nous allons définir la direction de notre secteur. L'expression (sector.name) pointe svelte vers l' id de l'objet dans l'itération . Clé analogique dans React.


Maintenant, notre grille ressemble à ceci


image


Il reste à préparer le secteur à travailler avec des événements de clic et de glissement.
L'événement touchmove, contrairement à mousemove, se déclenche uniquement sur l'élément sur lequel il a commencé. Par conséquent, nous ne pouvons pas saisir le moment où le pointeur s'est déplacé vers un autre secteur. Pour résoudre ce problème, dans le balisage de l'élément, nous allons stocker le nom actuel du secteur et sa valeur. Au moment de l'événement, nous déterminerons quel secteur se trouve sous le curseur et changerons sa valeur.

Notez que Svelte peut développer la construction {varName} en varName = {varName} . Cela permet de rouler très facilement les propriétés.


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} 

Si nous ajoutons une valeur autre que zéro dans notre magasin (store.js), cela devrait résulter:


4. Événements


Il est temps d'insuffler la vie à notre radar, de créer un gestionnaire qui prend un nœud à l'entrée, et à l'intérieur il traite les événements tactiles et de souris.


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); } }; } 

Maintenant, ajoutez simplement notre gestionnaire à l'élément radar svg via la directive 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> 

Le radar répond désormais aux clics et aux traînées.


6. Touche finale


Ajouter des légendes pour les secteurs et la description


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> 

Le radar devrait ressembler à ceci.


image


5. Bonus


J'ai un peu élargi la fonctionnalité radar, ajouté du stockage de données dans localStorage et un plan d'action. Vous pouvez essayer l' application Life-Checkup , le code source est disponible dans gitlab .


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


All Articles