Diseño y construcción de una aplicación web progresiva sin marco (Parte 2)

 

 

 

  • ¡Registro!
  • Implemente rápidamente. Implementar inteligentemente

  • Índice
    1. Herramientas de construcción
    2. Estructura del proyecto
  • Escribir una solicitud
  • Patrones de diseño
    1. Patrón de observador
    2. Estado de ahorro
    3. Aplicación-I-Ness
  • En el primer artículo de esta serie , su autor, un novato en JavaScript, se había fijado el objetivo de diseñar y codificar una aplicación web básica. La 'aplicación' se llamaría 'In/Out', una aplicación para organizar juegos en equipo. En este artículo, nos concentraremos en cómo se creó realmente la aplicación 'In/Out'.

     

    La razón de ser de esta aventura era impulsar un poco a su humilde autor en las disciplinas del diseño visual y la codificación JavaScript. La funcionalidad de la aplicación que había decidido crear no era diferente a una aplicación de "tareas pendientes". Es importante enfatizar que este no fue un ejercicio de pensamiento original. El destino era mucho menos importante que el viaje.

    ¿Quieres saber cómo terminó la aplicación? Apunte el navegador de su teléfono a https://io.benfrain.com .

    Aquí hay un resumen de lo que cubriremos en este artículo:

    • La configuración del proyecto y por qué opté por Gulp como herramienta de construcción;
    • Patrones de diseño de aplicaciones y lo que significan en la práctica;
    • Cómo almacenar y visualizar el estado de la aplicación;
    • cómo se aplicó CSS ​​a los componentes;
    • qué sutilezas de UI/UX se emplearon para hacer las cosas más "parecidas a una aplicación";
    • Cómo cambió el mandato a través de la iteración.

    Comencemos con las herramientas de construcción.

    Herramientas de construcción

    Para poner en funcionamiento mis herramientas básicas de TypeScipt y PostCSS y crear una experiencia de desarrollo decente, necesitaría un sistema de compilación.

    En mi trabajo diario, durante los últimos cinco años, he estado creando prototipos de interfaz en HTML/CSS y, en menor medida, JavaScript. Hasta hace poco, he usado Gulp con cualquier cantidad de complementos casi exclusivamente para satisfacer mis necesidades de compilación bastante humildes.

    Normalmente necesito procesar CSS, convertir JavaScript o TypeScript a JavaScript más compatible y, ocasionalmente, llevar a cabo tareas relacionadas como minimizar la salida de código y optimizar activos. Usar Gulp siempre me ha permitido resolver esos problemas con aplomo.

     

    Para aquellos que no están familiarizados, Gulp les permite escribir JavaScript para hacer "algo" con los archivos de su sistema de archivos local. Para usar Gulp, normalmente tienes un único archivo (llamado gulpfile.js) en la raíz de tu proyecto. Este archivo JavaScript le permite definir tareas como funciones. Puede agregar 'complementos' de terceros, que son esencialmente funciones adicionales de JavaScript que se ocupan de tareas específicas.

    Un ejemplo de tarea de trago

    Un ejemplo de tarea de Gulp podría ser usar un complemento para aprovechar PostCSS para procesar a CSS cuando cambia una hoja de estilo de creación ( gulp-postcss ). O compilar archivos TypeScript en JavaScript básico ( gulp-typescript ) a medida que los guarda. Aquí hay un ejemplo simple de cómo se escribe una tarea en Gulp. Esta tarea utiliza el complemento gulp 'del' para eliminar todos los archivos en una carpeta llamada 'build':

    var del = require("del");gulp.task("clean", function() { return del(["build/**/*"]);});

    requireAsigna el complemento dela una variable. Entonces gulp.taskse llama al método. Nombramos la tarea con una cadena como primer argumento (“limpiar”) y luego ejecutamos una función, que en este caso usa el método 'del' para eliminar la carpeta que se le pasó como argumento. Los símbolos de asterisco son patrones "glob" que esencialmente dicen "cualquier archivo en cualquier carpeta" de la carpeta de compilación.

    Las tareas de Gulp pueden volverse mucho más complicadas pero, en esencia, esa es la mecánica de cómo se manejan las cosas. La verdad es que con Gulp no necesitas ser un mago de JavaScript para salir adelante; Todo lo que necesitas son habilidades de copiar y pegar de tercer grado.

    Me quedé con Gulp como mi herramienta de compilación/ejecutador de tareas predeterminado durante todos estos años con una política de 'si no está roto; No intentes arreglarlo".

    Sin embargo, me preocupaba quedarme estancado en mis caminos. Es una trampa en la que es fácil caer. Primero, comienzas de vacaciones en el mismo lugar todos los años, luego te niegas a adoptar nuevas tendencias de moda y finalmente te niegas rotundamente a probar nuevas herramientas de construcción.

    Había escuchado muchas conversaciones en Internet sobre 'Webpack' y pensé que era mi deber probar un proyecto usando el novedoso brindis de los desarrolladores front-end cool-kids.

    paquete web

    Recuerdo claramente haber saltado al sitio webpack.js.org con gran interés. La primera explicación de qué es y qué hace Webpack comenzó así:

    import bar from './bar';

    ¿Que qué? En palabras del Dr. Evil, "Tírame un maldito hueso, Scott".

    Sé que es mi propio problema con el que tengo que lidiar, pero he desarrollado repulsión hacia cualquier explicación de codificación que mencione 'foo', 'bar' o 'baz'. Eso, más la total falta de descripción sucinta para qué servía realmente Webpack, me hizo sospechar que tal vez no era para mí.

     

    Profundizando un poco más en la documentación de Webpack, se ofreció una explicación un poco menos opaca: "En esencia, webpack es un paquete de módulos estáticos para aplicaciones JavaScript modernas".

    Mmm. Paquete de módulos estáticos. ¿Era eso lo que quería? No estaba convencido. Seguí leyendo, pero cuanto más leía, menos claro estaba. En aquel entonces, conceptos como gráficos de dependencia, recarga de módulos en caliente y puntos de entrada prácticamente no los entendía.

    Un par de noches después de investigar Webpack, abandoné cualquier idea de usarlo.

    Estoy seguro de que en la situación adecuada y en manos más experimentadas, Webpack es inmensamente poderoso y apropiado, pero me pareció completamente excesivo para mis humildes necesidades. La agrupación de módulos, la sacudida de árboles y la recarga de módulos en caliente sonaban geniales; Simplemente no estaba convencido de que los necesitara para mi pequeña 'aplicación'.

    Entonces, volvamos a Gulp.

    Sobre el tema de no cambiar las cosas por cambiar, otra pieza de tecnología que quería evaluar era Yarn over NPM para gestionar las dependencias del proyecto. Hasta ese momento, siempre había usado NPM y Yarn se promocionaba como una alternativa mejor y más rápida. No tengo mucho que decir sobre Yarn aparte de que si actualmente estás usando NPM y todo está bien, no necesitas molestarte en probar Yarn.

    Una herramienta que llegó demasiado tarde para valorarla para esta aplicación es Parceljs. Con una configuración cero y una recarga del navegador similar a BrowserSync respaldada, ¡desde entonces he encontrado una gran utilidad en él! Además, en defensa de Webpack, me dijeron que la versión 4 y posteriores de Webpack no requiere un archivo de configuración. Como anécdota, en una encuesta más reciente que realicé en Twitter , de los 87 encuestados, más de la mitad eligió Webpack en lugar de Gulp, Parcel o Grunt.

    Comencé mi archivo Gulp con funcionalidad básica para ponerlo en funcionamiento.

    Una tarea 'predeterminada' observaría las carpetas 'fuente' de hojas de estilo y archivos TypeScript y las compilaría en una buildcarpeta junto con el HTML básico y los mapas fuente asociados.

    También hice que BrowserSync funcionara con Gulp. Puede que no supiera qué hacer con un archivo de configuración de Webpack, pero eso no significaba que fuera una especie de animal. Tener que actualizar manualmente el navegador mientras se itera con HTML/CSS es tan del 2010 y BrowserSync le brinda ese breve ciclo de iteración y retroalimentación que es tan útil para la codificación de front-end.

    Aquí está el archivo gulp básico a partir del 11.6.2017

    Puedes ver cómo modifiqué el Gulpfile más cerca del final del envío, agregando minificación con ugilify :

    Estructura del proyecto

    Como consecuencia de mis elecciones tecnológicas, algunos elementos de la organización del código de la aplicación se fueron definiendo por sí solos. A gulpfile.jsen la raíz del proyecto, una node_modulescarpeta (donde Gulp almacena el código del complemento), una preCSScarpeta para las hojas de estilo de creación, una tscarpeta para los archivos TypeScript y una buildcarpeta para que viva el código compilado.

     

    La idea era tener un index.htmlque contuviera el 'shell' de la aplicación, incluyendo cualquier estructura HTML no dinámica y luego enlaces a los estilos y el archivo JavaScript que haría que la aplicación funcionara. En el disco, se vería así:

    build/node_modules/preCSS/ img/ partials/ styles.cssts/.gitignoregulpfile.jsindex.htmlpackage.jsontsconfig.json

    Configurar BrowserSync para ver esa buildcarpeta significaba que podía apuntar con mi navegador localhost:3000y todo estaba bien.

    Con un sistema de construcción básico implementado, una organización de archivos establecida y algunos diseños básicos para comenzar, ¡me había quedado sin material para la dilación que podía usar legítimamente para evitar que realmente construyera la cosa!

    Escribir una solicitud

    El principio de cómo funcionaría la aplicación era este. Habría un almacén de datos. Cuando se cargara JavaScript, cargaría esos datos, recorrería cada reproductor en los datos, crearía el HTML necesario para representar a cada reproductor como una fila en el diseño y los colocaría en la sección de entrada/salida adecuada. Entonces, las interacciones del usuario moverían al jugador de un estado a otro. Simple.

    Cuando llegó el momento de escribir la aplicación, los dos grandes desafíos conceptuales que debían comprenderse eran:

    1. Cómo representar los datos de una aplicación de manera que puedan ampliarse y manipularse fácilmente;
    2. Cómo hacer que la interfaz de usuario reaccione cuando se cambiaron datos a partir de la entrada del usuario.

    Una de las formas más sencillas de representar una estructura de datos en JavaScript es mediante notación de objetos. Esa frase suena un poco a informática. Más simplemente, un 'objeto' en la jerga de JavaScript es una forma práctica de almacenar datos.

    Considere este objeto JavaScript asignado a una variable llamada ioState(para estado de entrada/salida):

    var ioState = { Count: 0, // Running total of how many players RosterCount: 0; // Total number of possible players ToolsExposed: false, // Whether the UI for the tools is showing Players: [], // A holder for the players}

    Si realmente no conoce muy bien JavaScript, probablemente al menos pueda comprender lo que está pasando: cada línea dentro de las llaves es una propiedad (o 'clave' en el lenguaje de JavaScript) y un par de valores. Puede configurar todo tipo de cosas en una clave de JavaScript. Por ejemplo, funciones, matrices de otros datos u objetos anidados. He aquí un ejemplo:

    var testObject = { testFunction: function() { return "sausages"; }, testArray: [3,7,9], nestedtObject { key1: "value1", key2: 2, }}

    El resultado neto es que al utilizar ese tipo de estructura de datos se puede obtener y configurar cualquiera de las claves del objeto. Por ejemplo, si queremos establecer el recuento del objeto ioState en 7:

    ioState.Count = 7;

    Si queremos establecer un fragmento de texto con ese valor, la notación funciona así:

     

    aTextNode.textContent = ioState.Count;

    Puede ver que obtener valores y establecer valores para ese objeto de estado es simple en el lado de JavaScript. Sin embargo, reflejar esos cambios en la interfaz de usuario no lo es tanto. Esta es el área principal donde los marcos y las bibliotecas buscan eliminar el dolor.

    En términos generales, cuando se trata de actualizar la interfaz de usuario según el estado, es preferible evitar consultar el DOM, ya que esto generalmente se considera un enfoque subóptimo.

    Considere la interfaz de entrada/salida. Normalmente muestra una lista de jugadores potenciales para un juego. Están enumerados verticalmente, uno debajo del otro, a lo largo de la página.

    Quizás cada jugador esté representado en el DOM con una labelcasilla de verificación envolvente input. De esta manera, al hacer clic en un reproductor, el reproductor cambiará a "Entrada" en virtud de que la etiqueta hace que la entrada esté "marcada".

    Para actualizar nuestra interfaz, es posible que tengamos un 'escucha' en cada elemento de entrada en JavaScript. Al hacer clic o cambiar, la función consulta el DOM y cuenta cuántas de las entradas de nuestro reproductor están marcadas. Sobre la base de ese recuento, actualizaríamos algo más en el DOM para mostrarle al usuario cuántos jugadores están marcados.

    Consideremos el costo de esa operación básica. Estamos escuchando en múltiples nodos DOM para hacer clic/verificar una entrada, luego consultamos el DOM para ver cuántos de un tipo de DOM en particular están verificados y luego escribimos algo en el DOM para mostrarle al usuario, en cuanto a la interfaz de usuario, el número de jugadores. acabamos de contar.

    La alternativa sería mantener el estado de la aplicación como un objeto JavaScript en la memoria. Un clic en un botón/entrada en el DOM podría simplemente actualizar el objeto JavaScript y luego, en función de ese cambio en el objeto JavaScript, realizar una actualización de una sola pasada de todos los cambios de interfaz que sean necesarios. Podríamos omitir la consulta del DOM para contar los jugadores, ya que el objeto JavaScript ya contendría esa información.

    Entonces. Usar una estructura de objetos JavaScript para el estado parecía simple pero lo suficientemente flexible como para encapsular el estado de la aplicación en cualquier momento dado. La teoría de cómo se podría gestionar esto también parecía bastante sólida: ¿de eso debían tratarse frases como "flujo de datos unidireccional"? Sin embargo, el primer truco real sería crear algún código que actualizara automáticamente la interfaz de usuario en función de cualquier cambio en esos datos. Cursos gratis en Youtube

    La buena noticia es que personas más inteligentes que yo ya han descubierto estas cosas (¡ gracias a Dios! ). La gente ha estado perfeccionando enfoques para este tipo de desafío desde los albores de las aplicaciones. Esta categoría de problemas es el pan de cada día de los "patrones de diseño". El apodo de "patrón de diseño" me pareció esotérico al principio, pero después de investigar un poco, todo empezó a sonar menos a informática y más a sentido común.

    Patrones de diseño

    Un patrón de diseño, en el léxico de la informática, es una forma predefinida y probada de resolver un desafío técnico común. Piense en los patrones de diseño como el equivalente en codificación de una receta de cocina.

     

    Quizás la literatura más famosa sobre patrones de diseño sea "Patrones de diseño: elementos de software reutilizable orientado a objetos" de 1994. Aunque trata sobre C++ y charla trivial, los conceptos son transferibles. Para JavaScript, "Aprendiendo patrones de diseño de JavaScript" de Addy Osmani cubre un terreno similar. También puedes leerlo online gratis aquí .

    Patrón de observador

    Normalmente, los patrones de diseño se dividen en tres grupos: creacionales, estructurales y conductuales. Estaba buscando algo de comportamiento que ayudara a lidiar con la comunicación de cambios en las diferentes partes de la aplicación.

    Más recientemente, vi y leí un análisis profundo realmente excelente sobre la implementación de la reactividad dentro de una aplicación de Gregg Pollack. Aquí encontrará una publicación de blog y un video para su disfrute .

    Al leer la descripción inicial del patrón 'Observador' en Learning JavaScript Design Patternsestaba bastante seguro de que era el patrón para mí. Se describe así:

    El observador es un patrón de diseño en el que un objeto (conocido como sujeto) mantiene una lista de objetos que dependen de él (observadores), notificándoles automáticamente cualquier cambio de estado.

    Cuando un sujeto necesita notificar a los observadores sobre algo interesante que está sucediendo, transmite una notificación a los observadores (que puede incluir datos específicos relacionados con el tema de la notificación).

    La clave de mi entusiasmo fue que esto parecía ofrecer alguna forma de que las cosas se actualizaran cuando fuera necesario.

    Supongamos que el usuario hizo clic en un jugador llamado "Betty" para seleccionar que estaba "participado" en el juego. Es posible que deban suceder algunas cosas en la interfaz de usuario:

    1. Suma 1 al conteo de juegos
    2. Eliminar a Betty del grupo de jugadores 'Fuera'
    3. Agrega a Betty al grupo de jugadores 'In'

    La aplicación también necesitaría actualizar los datos que representan la interfaz de usuario. Lo que tenía muchas ganas de evitar era esto:

    playerName.addEventListener("click", playerToggle);function playerToggle() { if (inPlayers.includes(e.target.textContent)) { setPlayerOut(e.target.textContent); decrementPlayerCount(); } else { setPlayerIn(e.target.textContent); incrementPlayerCount(); }}

    El objetivo era tener un flujo de datos elegante que actualizara lo que se necesitaba en el DOM cuando y si se cambiaban los datos centrales.

    Con un patrón Observer, era posible enviar actualizaciones del estado y, por tanto, de la interfaz de usuario de forma bastante sucinta. Aquí hay un ejemplo, la función real utilizada para agregar un nuevo jugador a la lista:

    function itemAdd(itemString: string) { let currentDataSet = getCurrentDataSet(); var newPerson = new makePerson(itemString); io.items[currentDataSet].EventData.splice(0, 0, newPerson); io.notify({ items: io.items });}

    La parte relevante para el patrón Observer es el io.notifymétodo. Como eso nos muestra modificando la itemsparte del estado de la aplicación, permítanme mostrarles el observador que escuchó los cambios en los 'elementos':

     

    io.addObserver({ props: ["items"], callback: function renderItems() { // Code that updates anything to do with items... }});

    Tenemos un método de notificación que realiza cambios en los datos y luego observadores de esos datos que responden cuando se actualizan las propiedades que les interesan.

    Con este enfoque, la aplicación podría tener observables que observen cambios en cualquier propiedad de los datos y ejecutar una función cada vez que ocurra un cambio.

    Si está interesado en el patrón Observer por el que opté, lo describo más detalladamente aquí .

    Ahora existía un enfoque para actualizar la interfaz de usuario de manera efectiva según el estado. Aterciopelado. Sin embargo, esto todavía me dejó con dos problemas evidentes.

    Una era cómo almacenar el estado en las recargas/sesiones de la página y el hecho de que, a pesar de que la interfaz de usuario funcionaba, visualmente no era muy "parecida a una aplicación". Por ejemplo, si se presionaba un botón, la interfaz de usuario cambiaba instantáneamente en la pantalla. Simplemente no fue particularmente convincente.

    Abordemos primero el aspecto del almacenamiento.

    Estado de ahorro

    Mi principal interés desde el punto de vista del desarrollo al entrar en esto se centró en comprender cómo las interfaces de las aplicaciones podrían construirse y volverse interactivas con JavaScript. Cómo almacenar y recuperar datos de un servidor o abordar la autenticación de usuarios y los inicios de sesión estaba "fuera de alcance".

    Por lo tanto, en lugar de conectarme a un servicio web para las necesidades de almacenamiento de datos, opté por mantener todos los datos en el cliente. Existen varios métodos de plataforma web para almacenar datos en un cliente. Yo opté por localStorage.

    La API para localStorage es increíblemente simple. Configuras y obtienes datos como este:

    // Set somethinglocalStorage.setItem("yourKey", "yourValue");// Get somethinglocalStorage.getItem("yourKey");

    LocalStorage tiene un setItemmétodo al que le pasas dos cadenas. La primera es el nombre de la clave con la que desea almacenar los datos y la segunda cadena es la cadena real que desea almacenar. El getItemmétodo toma una cadena como argumento que le devuelve todo lo que esté almacenado bajo esa clave en localStorage. Bonito y sencillo.

    Sin embargo, entre las razones para no utilizar localStorage está el hecho de que todo debe guardarse como una "cadena". Esto significa que no puede almacenar directamente algo como una matriz u objeto. Por ejemplo, intente ejecutar estos comandos en la consola de su navegador:

    // Set somethinglocalStorage.setItem("myArray", [1, 2, 3, 4]);// Get somethinglocalStorage.getItem("myArray"); // Logs "1,2,3,4"

    Aunque intentamos establecer el valor de 'myArray' como una matriz; cuando lo recuperamos, se había almacenado como una cadena (tenga en cuenta las comillas alrededor de '1,2,3,4').

    Ciertamente puede almacenar objetos y matrices con localStorage, pero debe tener en cuenta que es necesario convertirlos de cadenas.

    Entonces, para escribir datos de estado en localStorage, se escribieron en una cadena con un JSON.stringify()método como este:

     

    const storage = window.localStorage;storage.setItem("players", JSON.stringify(io.items));

    Cuando era necesario recuperar los datos del almacenamiento local, la cadena se convertía nuevamente en datos utilizables con un JSON.parse()método como este:

    const players = JSON.parse(storage.getItem("players"));

    El uso localStoragesignificaba que todo dependía del cliente y eso significaba que no había problemas con servicios de terceros o almacenamiento de datos.

    Los datos ahora persistían en actualizaciones y sesiones: ¡Sí! La mala noticia fue que localStorage no sobrevive a que un usuario vacíe los datos de su navegador. Cuando alguien hiciera eso, se perderían todos sus datos de entrada/salida. Ésa es una deficiencia grave.

    No es difícil darse cuenta de que "localStorage" probablemente no sea la mejor solución para aplicaciones "adecuadas". Además del problema de cadena antes mencionado, también es lento para trabajos serios ya que bloquea el "hilo principal". Están surgiendo alternativas, como KV Storage , pero por ahora, tome nota mental de advertir su uso en función de su idoneidad.

    A pesar de la fragilidad de guardar datos localmente en el dispositivo de un usuario, se resistió a conectarse a un servicio o base de datos. En cambio, el problema se evitó ofreciendo una opción de "cargar/guardar". Esto permitiría a cualquier usuario de In/Out guardar sus datos como un archivo JSON que podría cargarse nuevamente en la aplicación si fuera necesario.

    Esto funcionó bien en Android pero de manera mucho menos elegante en iOS. En un iPhone, resultó en un derroche de texto en la pantalla como este:

    ( Vista previa grande )

    Como puedes imaginar, no fui el único que reprendió a Apple a través de WebKit por esta deficiencia. El error relevante estaba aquí .

    Al momento de escribir este artículo, este error tiene una solución y un parche, pero aún no ha llegado a iOS Safari. Supuestamente, iOS13 lo soluciona, pero está en Beta mientras escribo.

    Entonces, para mi producto mínimo viable, se abordó el almacenamiento. ¡Ahora era el momento de intentar hacer las cosas más "parecidas a una aplicación"!

    Aplicación-I-Ness

    Resulta que después de muchas discusiones con mucha gente, definir exactamente qué significa "app like" es bastante difícil.

    Al final, me decidí por que "parecido a una aplicación" fuera sinónimo de una elegancia visual que normalmente falta en la web. Cuando pienso en las aplicaciones que se sienten bien al usarlas, todas cuentan con movimiento. No es gratuito, sino un movimiento que se suma a la historia de tus acciones. Podrían ser las transiciones de página entre pantallas, la forma en que aparecen los menús. Es difícil describirlo con palabras, pero la mayoría de nosotros lo sabemos cuando lo vemos.

    La primera pieza de estilo visual que se necesitaba era cambiar los nombres de los jugadores hacia arriba o hacia abajo de "Entrada" a "Fuera" y viceversa cuando se seleccionaban. Hacer que un jugador se moviera instantáneamente de una sección a otra fue sencillo, pero ciertamente no "como una aplicación". Se espera que una animación al hacer clic en el nombre de un jugador enfatice el resultado de esa interacción: el jugador pasando de una categoría a otra.

     

    Como muchos de este tipo de interacciones visuales, su aparente simplicidad contradice la complejidad que implica lograr que funcione bien.

    Se necesitaron algunas iteraciones para lograr el movimiento correcto, pero la lógica básica era la siguiente:

    • Una vez que se hace clic en un 'jugador', capture dónde está ese jugador, geométricamente, en la página;
    • Mida a qué distancia está la parte superior del área a la que el jugador debe moverse si sube ('In') y qué tan lejos está la parte inferior, si baja ('Out');
    • Si va hacia arriba, se debe dejar un espacio igual a la altura de la fila de jugadores a medida que el jugador sube y los jugadores de arriba deben colapsar hacia abajo al mismo ritmo que el tiempo que le toma al jugador subir para aterrizar en el espacio. desocupado por los jugadores 'In' existentes (si existen) que bajan;
    • Si un jugador sale y se mueve hacia abajo, todo lo demás debe moverse hacia arriba al espacio que queda y el jugador debe terminar debajo de cualquier jugador "fuera" actual.

    ¡Uf! Fue más complicado de lo que pensaba en inglés, ¡sin mencionar JavaScript!

    Había complejidades adicionales que considerar y probar, como las velocidades de transición. Al principio, no estaba claro si una velocidad de movimiento constante (por ejemplo, 20 px cada 20 ms) o una duración constante del movimiento (por ejemplo, 0,2 s) se vería mejor. El primero era un poco más complicado ya que la velocidad debía calcularse "sobre la marcha" en función de la distancia que debía recorrer el jugador: una distancia mayor requería una duración de transición más larga.

    Sin embargo, resultó que una duración de transición constante no sólo era más simple en el código; en realidad produjo un efecto más favorable. La diferencia era sutil, pero este es el tipo de opciones que sólo puedes determinar una vez que hayas visto ambas opciones.

    De vez en cuando, al intentar lograr este efecto, llamaba la atención un error visual, pero era imposible deconstruirlo en tiempo real. Descubrí que el mejor proceso de depuración era crear una grabación QuickTime de la animación y luego revisarla fotograma a fotograma. Invariablemente, esto revelaba el problema más rápido que cualquier depuración basada en código.

    Al mirar el código ahora, puedo apreciar que en algo más allá de mi humilde aplicación, es casi seguro que esta funcionalidad podría escribirse de manera más efectiva. Dado que la aplicación conocería la cantidad de jugadores y la altura fija de las tablillas, debería ser completamente posible realizar todos los cálculos de distancia solo en JavaScript, sin ninguna lectura de DOM.

    No es que lo que se envió no funcione, es sólo que no es el tipo de solución de código que se exhibiría en Internet. Oh espera.

    Otras interacciones tipo "aplicación" fueron mucho más fáciles de lograr. En lugar de que los menús simplemente aparezcan y salgan con algo tan simple como alternar una propiedad de visualización, se avanzó mucho simplemente exponiéndolos con un poco más de delicadeza. Todavía se activaba de forma sencilla, pero CSS estaba haciendo todo el trabajo pesado:

    .io-EventLoader { position: absolute; top: 100%; margin-top: 5px; z-index: 100; width: 100%; opacity: 0; transition: all 0.2s; pointer-events: none; transform: translateY(-10px); [data-evswitcher-showing="true"] { opacity: 1; pointer-events: auto; transform: none; }}

    Allí, cuando data-evswitcher-showing="true"se activaba el atributo en un elemento principal, el menú aparecía gradualmente, se transformaba nuevamente a su posición predeterminada y los eventos de puntero se volvían a habilitar para que el menú pudiera recibir clics.

    Metodología de la hoja de estilo ECSS

    Notarás en ese código anterior que, desde el punto de vista de la creación, las anulaciones de CSS se anidan dentro de un selector principal. Esa es la forma en que siempre prefiero escribir hojas de estilo de interfaz de usuario; una única fuente de verdad para cada selector y cualquier anulac






    Tal vez te puede interesar:

    1. ¿Qué es Redux? Una guía para el diseñador
    2. Diseño y construcción de una aplicación web progresiva sin marco (Parte 3)
    3. Escribir un motor de aventuras de texto multijugador en Node.js: diseño del servidor Game Engine (Parte 2)
    4. Componentes de diseño en React

    Diseño y construcción de una aplicación web progresiva sin marco (Parte 2)

    Diseño y construcción de una aplicación web progresiva sin marco (Parte 2)

    ¡Registro! Implemente rápidamente. Implementar inteligentemente Índice Herramientas de construcción

    programar

    es

    https://pseint.es/static/images/programar-diseno-y-construccion-de-una-aplicacion-web-progresiva-sin-marco-parte-2-994-0.jpg

    2024-04-04

     

    Diseño y construcción de una aplicación web progresiva sin marco (Parte 2)
    Diseño y construcción de una aplicación web progresiva sin marco (Parte 2)

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20