Reimaginando aplicaciones de una sola página con mejoras progresivas

 

 

 

  • Implemente rápidamente. Implementar inteligentemente
  • Creación y mantenimiento de sistemas de diseño exitosos, con Brad Fost

  • Índice
    1. Vistas de escritura
    2. Navegación
    3. Una vista a la vez
    4. La vista predeterminada
      1. Es la hora
    5. El hashchangeevento
    6. Abstracción
      1. Notas
    7. Vistas completamente estáticas
      1. Vistas estáticas con funcionalidad mejorada
    8. Conclusión
      1. Otras lecturas

    Las aplicaciones de una sola página tienden a tomar la forma de tiempos de ejecución, ejecutables de JavaScript desplegados como tiendas emergentes en elementos vacíos. En este artículo, Heydon Pickering presentará una solución para diseñar aplicaciones progresivas de una sola página utilizando poco más que un par de trucos de CSS, menos de 0,5 KB de JavaScript y, lo que es más importante, algo de HTML estático. No es una solución perfecta ni completa, pero da testimonio de la noción de que es posible lograr aplicaciones de una sola página indexables, robustas y de alto rendimiento: puede adoptar estándares web y, al mismo tiempo, aprovechar los beneficios de compartir datos y funcionalidad entre diferentes pantallas de interfaz en una única web. página.

     

    ¿Cuál es la diferencia entre una página web y una aplicación web? Aunque tendemos a identificar documentos con lectura y aplicaciones con interacción, la mayoría de las aplicaciones basadas en web son del tipo combinado : los usuarios pueden consumir información y realizar tareas en el mismo lugar. De todos modos, la forma en que abordamos la creación de aplicaciones web generalmente prescinde de algunas de las virtudes simples de la web legible.

    Las aplicaciones de una sola página tienden a tomar la forma de tiempos de ejecución, ejecutables de JavaScript desplegados como tiendas emergentes en bodyelementos vacíos. Son temporales, improvisados ​​y no se pueden curvar : su contenido no está realmente allí sin que se ejecute un script. También son frágiles y de bajo rendimiento porque, al servicio de la uniformidad y conveniencia arquitectónica, hacen que toda la navegación, el manejo de datos e incluso la visualización básica del contenido sean responsabilidad de una sola cosa: JavaScript del lado del cliente.

     

    Recientemente, ha habido un movimiento hacia aplicaciones “isomorfas” (o “ universales ”): aplicaciones que pueden ejecutar el mismo código en el cliente y el servidor, enviando HTML prerenderizado desde el servidor antes de delegar al código del lado del cliente. Este enfoque (posible usar Express como servidor y React como motor de renderizado , por ejemplo) es un gran paso hacia una arquitectura de aplicaciones web más potente y robusta.

    Pero el isomorfismo seguramente no es la única forma de lograr una mejora progresiva para aplicaciones de una sola página. Estoy buscando algo más flexible y con menos configuración, una nueva filosofía que aproveche el comportamiento estándar del navegador y que pueda combinar prosa estática e indexable con interactividad adornada con JavaScript, en lugar de simplemente "transferirlo" a JavaScript.

    Esta pequeña exposición no es más que la noción de hacer las cosas The Web Way™ con algunos conceptos y técnicas vagamente confederados, pero creo que podrías tomarla y convertirla en algo especial.

    Vistas de escritura

    En una aplicación típica de una sola página, la representación de vistas (es decir, pantallas individuales) y el enrutamiento entre ellas se convierten en una preocupación de JavaScript. Es decir, las ubicaciones son definidas, evaluadas y creadas enteramente por lo que, hasta hace poco, era una tecnología considerada complementaria a este tipo de comportamiento. Llámame ludita , pero no voy a utilizar JavaScript para esto en absoluto. Hereje, voy a dejar que HTML y el navegador se encarguen de ello.

    Comenzaré creando una página HTML y haciendo que mainparte de esa página sea mi contenedor de vistas:

    main role="main" /* Views go here. *//main

    Luego, comenzaré a construir vistas individuales, colocando cada una como elemento secundario de main. Cada vista debe llevar un id. Esto se utilizará como parte de nuestra "solución de enrutamiento". También debe tener un encabezado de primer nivel: las vistas se mostrarán una a la vez, como el único contenido perceptible de la página, por lo que es preferible para la accesibilidad del lector de pantalla.

    div h1Some view/h1 !-- the static view content, enhanceable with JavaScript --/div

    Por brevedad y para subrayar la importancia de trabajar directamente en HTML, estoy codificando mis vistas a mano. Es posible que prefiera compilar sus vistas a partir de datos utilizando, por ejemplo, Manubrios y un script Node.js, en cuyo caso cada vista dentro de su {{#each}}bloque podría verse como la siguiente. Observe que estoy usando un asistente de Manillar para crear dinámicamente la propiedadid de la vista .title

     

    div h1{{title}}/h1 {{{content}}}/div

    ¿Quizás usar PHP para generar el contenido a partir de una base de datos MySQL sea más lo tuyo? Realmente no es importante cómo compilas tus vistas siempre que el contenido se entregue precompilado al cliente. Parte del contenido y la funcionalidad deberían estar disponibles en ausencia de secuencias de comandos del lado del cliente . Luego, podremos mejorarlo progresivamente, sólo en los casos en los que realmente queramos mejorarlo progresivamente. Como explicaré, mi método preservará el contenido estático dentro de la aplicación simplemente como eso: contenido estático.

    Navegación

    Al no estar interesado en romper con las convenciones, creo que mi aplicación de una sola página se beneficiaría de un bloque de navegación, que permita a los usuarios desplazarse entre las vistas. Sobre el mainárea de visualización, podría proporcionar algo como esto:

    nav role="navigation" ul lia href="#the-default-view"the default view/a/li lia href="#some-view"some view/a/li lia href="#another-view"another view/a/li /ul/nav

    Mis vistas son fragmentos de documentos, identificados por su ids, y se puede acceder a ellos mediante enlaces que llevan este identificador o "hash". Entonces, cuando los usuarios hacen clic en el primer enlace que apunta a #the-default-view, serán transportados a esa vista. Si actualmente no está visible en la ventana gráfica, el navegador lo desplazará hasta que esté visible. Simultáneamente, la URL se actualizará para reflejar la nueva ubicación. Para determinar dónde se encuentra en la aplicación, sólo necesita consultar la URL:

    https://my-app-thing.com#the-default-view

    Como se puede imaginar, aprovechar el comportamiento estándar del navegador para recorrer contenido estático es realmente bastante eficaz. Se puede esperar que funcione sin las trabas de JavaScript e incluso tendrá éxito cuando JavaScript falla. Aunque mi “aplicación” se parece más a una página de Wikipedia que al tipo de cosas que estás familiarizado con las creadas con AngularJS, la parte de navegación de mi enrutamiento ahora está completa.

    Nota: Debido a que los navegadores conformes envían el foco a los fragmentos de página , la accesibilidad del teclado ya se aborda aquí. Puedo mejorar la accesibilidad del teclado cuando, finalmente, se emplea JavaScript. Más sobre eso más adelante.

    Una vista a la vez

    Como consultor de accesibilidad, gran parte de mi trabajo gira en torno a conciliar el estado y el comportamiento con la apariencia de estas cosas. En este punto, el comportamiento de cambiar rutas dentro de nuestra aplicación ya es compatible, pero la aplicación no se ve ni se siente como una aplicación de una sola página porque cada vista está siempre presente, en lugar de ser mutuamente excluyente. Solo deberíamos mostrar la vista a la que ha navegado el usuario.

     

    ¿Es este el punto de inflexión en el que empiezo a mejorar progresivamente con JavaScript? No aún no. En este caso, aprovecharé :targetla pseudoclase de CSS. La mejora progresiva no significa simplemente “agregar JavaScript”: nuestra página web debería funcionar bien sin JavaScript ni CSS.

    main * { display: none;}main *:target { display: block;}

    La :targetpseudoclase se relaciona con el elemento que coincide con el identificador de fragmento en la URL. En otras palabras, si la URL es , entonces solo se habrá aplicado https://my-app-thing.com#some-viewel elemento con idof . Para "cargar" esa vista (y ocultar las otras vistas), todo lo que hay que hacer es hacer clic en un enlace con el correspondiente . Lo creas o no, estoy usando enlaces como enlaces, sin secuestrarlos ni suprimir su funcionalidad predeterminada, como lo harían la mayoría de las aplicaciones de una sola página (incluidas las aplicaciones isomorfas renderizadas por el cliente).some-viewdisplay: blockhref

    a href="#some-view"some view/a

    Ahora se parece más a una aplicación de una sola página (que, a su vez, está diseñada para sentirse como si estuviera navegando entre páginas web separadas). Si así lo deseo, podría ir un paso más allá añadiendo algo de animación.

    main * { display: none;}@keyframes pulse { 0% { transform: scale(1) } 50% { transform: scale(1.05) } 100% { transform: scale(1) }}main *:target { display: block; animation: pulse 0.5s linear 1;}

    ¡Elegante! Y, hay que admitirlo, algo inútil, pero hay algo que decir a favor de una indicación visual de que el contexto ha cambiado, especialmente cuando el cambio de vista es instantáneo. He configurado un Codepen para que veas el efecto. Tenga en cuenta que el botón "atrás" del navegador funciona como se esperaba, porque ningún JavaScript lo ha secuestrado ni lo ha pisoteado. Afortunadamente, la animación se activa a través de un enlace en la página o con los botones "atrás" y "adelante".

    Todo funciona muy bien hasta ahora, excepto que no se muestra ninguna vista al https://my-app-thing.comrecibir el primer golpe. ¡Podemos arreglar esto! No, no con JavaScript, sino nuevamente con una mejora de CSS. Si usáramos JavaScript aquí, todo nuestro sistema de enrutamiento dependería de él y todo se perdería.

    La vista predeterminada

    Debido a que no puedo confiar en que los usuarios naveguen https://my-app-thing.com#the-default-viewsegún lo que yo diga, y debido a que :targetnecesita que el identificador de fragmento #the-default-viewfuncione, tendré que probar algo más para mostrar esa vista predeterminada.

    Resulta que esto se puede lograr controlando el orden de las fuentes y siendo un poco monstruoso con los selectores CSS . Primero, haré que mi vista predeterminada sea el último de los elementos de vista hermanos en el marcado. Esto es perfectamente aceptable desde el punto de vista de la accesibilidad porque las vistas se "cargan" una a la vez y las demás se ocultan de la tecnología de asistencia mediante el uso de display: none. El orden no es pertinente.

     

    main role="main" div h1some view/h1 !-- … -- /div div h1another view/h1 !-- … -- /div div h1the default view/h1 !-- … -- /div/main

    Me parece correcto poner la vista predeterminada al final. Es como una alternativa. Ahora podemos adaptar el CSS:

    main * { display: none;}main *:last-child { display: block;}@keyframes pulse { 0% { transform: scale(1) } 50% { transform: scale(1.05) } 100% { transform: scale(1) }}main *:target { display: block; animation: pulse 0.5s linear 1;}main *:target ~ * { display: none;}

    Hay dos nuevos bloques de declaración: el segundo y definitivo. El segundo anula el primero para mostrar nuestra *:last-childvista predeterminada. Esto ahora será visible cuando el usuario presione https://my-app-thing.com. El bloque final, que utiliza el combinador de hermanos general, se aplica display: nonea cualquier elemento que siga al :targetelemento. Debido a que nuestra vista predeterminada es la última, esta regla siempre se aplicará a ella, pero solo si :targetexiste un elemento. (Debido a que CSS no funciona al revés, un :first-childelemento predeterminado no sería accesible desde un :targetelemento hermano que aparece después de él). Fulares para bebés

    Intente recargar Codepen solo con la URL raíz (sin hash en la barra de direcciones) para ver esto en la práctica.

    Es la hora

    Hemos recorrido un largo camino sin utilizar JavaScript. El truco ahora consiste en agregar juiciosamente el comportamiento de JavaScript , mejorando lo que se ha logrado hasta ahora sin reemplazarlo. Deberíamos poder reaccionar a los cambios de vista con JavaScript sin que esos cambios de vista caigan en el ámbito de JavaScript. Cualquier cosa que no llegue a esto sería un exceso de ingeniería, lo que disminuiría el rendimiento y la confiabilidad.

    Voy a utilizar un mínimo de JavaScript sencillo y con buen soporte, no jQuery ni ninguna otra biblioteca auxiliar: el esqueleto de la aplicación debe seguir siendo pequeño pero extensible.

    El hashchangeevento

    Como se indicó, los marcos de aplicaciones web populares tienden a representar vistas con JavaScript. Luego permiten ganchos de devolución de llamada, como el de Meteor Template.my-template.rendered, para aumentar la vista en el punto en que está disponible. Incluso a las aplicaciones isomórficas les gusta usar enrutamiento y renderizado basados ​​en scripts si tienen la oportunidad. Mi pequeña aplicación no muestra vistas sino que las revela . Sin embargo, es muy probable que, en algunos casos, quiera actuar sobre una vista recién revelada con JavaScript, cuando llegue.

    Afortunadamente, la API web nos ofrece el tipo de evento extremadamente compatible (desde Internet Explorer 8 en adelante) hashchange, que se activa cuando cambia el identificador de fragmento de la URL. Esto tiene un efecto similar pero, fundamentalmente, no depende de que JavaScript represente la vista (desde la cual emitiría un evento personalizado) para proporcionarnos un gancho.

    En el siguiente script ( que se muestra en otro Codepen ), utilizo el hashchangeevento para registrar la identidad de la vista actual, que también funciona como idelemento principal de esa vista. Como puedes imaginar, funciona sin importar cómo cambies esa URL, incluso usando el botón "atrás".

     

    window.addEventListener('hashchange', function() { console.log('this view's id is ', location.hash.substr(1));});

    Podemos aplicar las operaciones DOM a nuestra vista configurando una variable dentro de este controlador de eventos, como viewElem, para indicar el elemento raíz de la vista. Luego, podemos apuntar a elementos específicos de la vista con expresiones como viewElem.getElementsByClassName('button')[0]y así sucesivamente.

    window.addEventListener('hashchange', function() { var viewID = location.hash.slice(1); var viewElem = document.getElementById(viewID); viewElem.innerHTML = 'pView loaded!/p';});

    Abstracción

    Desconfío de la abstracción porque puede convertirse en su propio fin, haciendo que la lógica del programa se vuelva opaca en el proceso. Pero las cosas se convertirán rápidamente en un lío de ifdeclaraciones desagradables si sigo en esta línea y empiezo a admitir diferentes funcionalidades para vistas individuales. También debería abordar la cuestión de cómo llenar el ámbito global . Entonces, voy a tomar prestado un patrón singleton común : definir un objeto con nuestra funcionalidad dentro de una función autoejecutable que luego se adjunta al archivo window. Aquí es donde definiré mis rutas y métodos de alcance de aplicación.

    En el siguiente ejemplo, mi appobjeto contiene cuatro propiedades: routespara definir cada ruta por nombre, defaultpara definir la raíz predeterminada (que se muestra por primera vez), routeChangepara manejar un cambio de ruta (un cambio de hash) y initpara activarse una vez para iniciar la aplicación. (cuando JavaScript esté disponible) usando app.init().

    (function() { var app = { // routes (i.e. views and their functionality) defined here 'routes': { 'some-view': { 'rendered': function() { console.log('this view is "some-view"'); } }, 'another-view': { 'rendered': function() { console.log('this view is "another-view"'); app.routeElem.innerHTML = 'pThis JavaScript content overrides the static content for this view./p'; } } }, // The default view is recorded here. A more advanced implementation // might query the DOM to define it on the fly. 'default': 'the-default-view', 'routeChange': function() { app.routeID = location.hash.slice(1); app.route = app.routes[app.routeID]; app.routeElem = document.getElementById(app.routeID); app.route.rendered(); }, // The function to start the app 'init': function() { window.addEventListener('hashchange', function() { app.routeChange(); }); // If there is no hash in the URL, change the URL to // include the default view's hash. if (!window.location.hash) { window.location.hash = app.default; } else { // Execute routeChange() for the first time app.routeChange(); } } }; window.app = app;})();app.init();

    Notas

    • El contexto para la ruta actual se establece dentro app.routeChange, usando la sintaxis app.routes[app.routeID], donde app.routeIDes igual a window.location.hash.substr(1).
    • Cada ruta nombrada tiene su propia renderedfunción, que se ejecuta dentro de app.routeChangewith app.route.rendered().
    • El hashchangeoyente está apegado al windowdurante init.
    • Para que se ejecute cualquier JavaScript que deba ejecutarse en la vista predeterminada cuando https://my-app-thing.com se carga, fuerzo esa URL con window.location.hash = app.default, lo que activa hashchangela ejecución , incluida la función app.routeChange()de la ruta predeterminada .rendered()
    • Si el usuario accede por primera vez a la aplicación en una URL hash específica (como https://my-app-thing.com#a-certain-view), entonces la renderedfunción de esta vista se ejecutará si hay una asociada con ella.
    • Si comento app.init(), mis vistas seguirán "procesadas", seguirán siendo navegables, tendrán estilo y estarán animadas, y contendrán mi contenido estático.

    Una cosa para la que podría usar la renderedfunción sería mejorar la accesibilidad del teclado y del lector de pantalla enfocando el archivo h1. Cuando h1está enfocado, anuncia en los lectores de pantalla en qué vista se encuentra el usuario y coloca el foco del teclado en una posición conveniente en la parte superior del contenido de esa vista.

     

    'rendered': function() { app.routeElem.querySelector('h1').setAttribute('tabindex', '-1'); app.routeElem.querySelector('h1').focus(); }

    Hay otro Codepen disponible utilizando este pequeño “marco” de aplicación. Probablemente haya formas más claras e incluso más concisas (!) de escribir esto, pero todos los fundamentos están ahí para explorar y reorganizar. También agradecería cualquier sugerencia para mejorar. Quizás se pueda lograr algo con hashchangela oldURLpropiedad , que (para nuestros propósitos) hace referencia a la ruta anterior.

    app.prevRoute = app.routes[e.oldURL.split("#")[1]];

    Entonces, cada ruta, en lugar de la renderedfunción singular, podría tener funciones enteredy exited. Entre otras cosas, entonces sería posible agregar y eliminar detectores de eventos.

    app.prevRoute.exited();

    Vistas completamente estáticas

    Los más atentos habrán notado que la vista predeterminada, identificada como app.default, the-default-viewen este caso no figura en el app.routesobjeto. Esto significa que nuestra aplicación arrojará un error cuando intente ejecutar su renderedfunción inexistente. La vista seguirá apareciendo bien, pero podemos eliminar el error de todos modos comprobando primero la existencia de la ruta:

     

    if (app.route) { app.route.rendered();}

    La implicación es que pueden existir “vistas” completamente estáticas, libres de errores, al lado de vistas que están (potencialmente) altamente aumentadas por JavaScript. Esto rompe con la normalidad de las aplicaciones de una sola página, en la que se perdería la capacidad de ofrecer contenido prerenderizado estático al generar todo el contenido desde cero en el cliente, bueno, a menos que JavaScript falle y solo se muestre una página en blanco. Se pueden encontrar muchos ejemplos de ese comportamiento desafortunado en Suspiro , JavaScript .

    ( Nota: como en realidad tengo contenido estático para compartir, querré agregar mi appscript después del contenido en la parte inferior de la página, para que no bloquee su representación... Pero eso ya lo sabías).

    Vistas estáticas con funcionalidad mejorada

    Por supuesto, también puedes mezclar contenido estático y entregado mediante JavaScript en la misma vista. Como parte de la renderedfunción de una vista particular, podría insertar nuevos nodos DOM y adjuntar nuevos controladores de eventos, por ejemplo. Tal vez agregue algo de AJAX para obtener algunos datos nuevos antes de compilar una plantilla en lugar del HTML renderizado por el servidor. Podría incluir un formulario que ejecute un script PHP en el servidor cuando JavaScript no esté disponible y que devuelva al usuario a la vista específica del formulario con header('Location: https://my-app-thing.com#submission-form'). También puede manejar parámetros de consulta mediante URL como https://my-app-thing.com/?foo=bar#some-view.

    Es completamente flexible y le permite combinar cualquier tarea de compilación, tecnologías de servidor, estructuras HTML y bibliotecas de JavaScript que desee. Todo lo que este enfoque hace “listo para usar” es mantener las cosas en una página web de una manera responsable y progresiva.

    Independientemente de lo que desee lograr, tiene la opción de adjuntar funciones, datos y otras propiedades en el alcance global de la aplicación ( app.custom()) o en vistas específicas ( app.routes['route-name'].custom()), como en una aplicación "real" de una sola página. Su responsabilidad, entonces, es combinar contenido estático y funcionalidad mejorada de la manera más fluida posible y evitar relegar su contenido estático a una simple alternativa superficial.

    Conclusión

    En este artículo, presenté una solución para diseñar aplicaciones progresivas de una sola página usando poco más que un par de trucos de CSS, menos de 0,5 KB de JavaScript y, lo que es más importante, algo de HTML estático. No es una solución perfecta ni completa, sólo un esqueleto modesto, pero da testimonio de la noción de que se pueden lograr aplicaciones de una sola página indexables, robustas y de alto rendimiento: puede adoptar estándares web mientras cosecha los beneficios de compartir datos y funcionalidad entre diferentes interfaces. pantallas en una sola página web. Eso es todo lo que hace que una aplicación de una sola página sea una aplicación de una sola página, en realidad. Todo lo demás es un complemento.

    Si tiene alguna sugerencia de mejora o desea plantear alguna pregunta o inquietud, deje un comentario. No estoy interesado en construir un marco “maduro” (léase: sobrediseñado), pero sí en resolver problemas importantes de la manera más simple posible. Por encima de todo, quiero que nos ayudemos unos a otros a crear aplicaciones que no estén sólo en la web, sino también en la web.

    Si no está seguro de lo que quiero decir con esto o se pregunta por qué me entusiasma tanto, le recomiendo leer Diseño web adaptativo de Aaron Gustafson . Si eso es demasiado por el momento, hágase un favor y lea el breve artículo “ Por dónde empezar ” de Jeremy Keith.

    Otras lecturas

    • Rendimiento percibido
    • Gestión de la percepción
    • Precarga: ¿Para qué sirve?
    • Preparándose para HTTP/2
    • Todo lo que necesitas saber sobre AMP
    • Mejora del rendimiento de la revista Smashing

    (jb, ml, al, mrn)Explora más en

    • Codificación
    • CSS
    • javascript
    • Actuación





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Reimaginando aplicaciones de una sola página con mejoras progresivas

    Reimaginando aplicaciones de una sola página con mejoras progresivas

    Implemente rápidamente. Implementar inteligentemente Creación y mantenimiento de sistemas de diseño exitosos, con Brad Fost Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-reimaginando-aplicaciones-de-una-sola-pagina-con-mejoras-progresivas-878-0.jpg

    2024-05-20

     

    Reimaginando aplicaciones de una sola página con mejoras progresivas
    Reimaginando aplicaciones de una sola página con mejoras progresivas

    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