Cómo posicionar dinámicamente elementos en el DOM con JavaScript

Cómo usar JavaScript para manipular dinámicamente elementos DOM en relación con otros elementos DOM.

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start :

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

¿Por qué?

A primera vista, esto puede parecer un poco tonto. ¿Por qué querríamos hacer esto? Bueno, cuando comienzas a construir interfaces más complejas, aunque muchos patrones de interfaz de usuario se intentan mejor a través de CSS primero , a veces, eso complica las cosas más de lo necesario. Cuando ese es el caso de su propia aplicación, es bueno saber cómo aplicar estilos a través de JavaScript para manejar los cambios en su interfaz de usuario para evitar CSS desordenado o frágil.

Configurando nuestro caso de prueba

Para este tutorial, vamos a trabajar con un componente Joystick. Esta es la mitad de la interfaz de usuario del marco Joystick que acabamos de configurar. Esto nos permitirá crear una interfaz de usuario rápidamente utilizando HTML, CSS y JavaScript sin formato.

Para empezar, en la aplicación que se creó para nosotros cuando ejecutamos joystick create app , abre el /ui/pages/index/index.js expediente. Una vez que lo tenga, reemplace el contenido con lo siguiente:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

Aquí, estamos reemplazando el componente de ejemplo existente que está asignado a la ruta raíz en nuestra aplicación http://localhost:2600/ (o simplemente / ) con un componente de esqueleto que podemos usar para construir nuestro caso de prueba.

A continuación, reemplacemos ese <div></div> siendo devuelto por el render() (este es el HTML que se representará o "dibujará" en la pantalla) con una lista de "tarjetas" que posicionaremos dinámicamente más tarde con JavaScript:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div class="index">
        <ul class="cards">
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          <li>
            <h2>Ab recusandae minima commodi sed pariatur.</h2>
            <p>Velit in voluptatum quia consequatur fuga et repellendus ut cupiditate. Repudiandae dignissimos dolores qui. Possimus nihil laboriosam enim dolorem vitae accusantium accusamus dolor. Tenetur fuga omnis et est accusantium dolores. Possimus vitae aliquid. Vitae commodi et autem vitae rerum.</p>
          </li>
          <li>
            <h2>Voluptatem ipsa sed illum numquam aliquam sint.</h2>
            <p>Suscipit quis error dolorum sed recusandae recusandae est. Et tenetur perferendis sequi itaque similique. Porro facere qui saepe alias. Qui itaque corporis explicabo itaque. Quibusdam vel expedita odio quaerat libero veniam praesentium minus.</p>
          </li>
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          <li>
            <h2>Ab recusandae minima commodi sed pariatur.</h2>
            <p>Velit in voluptatum quia consequatur fuga et repellendus ut cupiditate. Repudiandae dignissimos dolores qui. Possimus nihil laboriosam enim dolorem vitae accusantium accusamus dolor. Tenetur fuga omnis et est accusantium dolores. Possimus vitae aliquid. Vitae commodi et autem vitae rerum.</p>
          </li>
          <li>
            <h2>Voluptatem ipsa sed illum numquam aliquam sint.</h2>
            <p>Suscipit quis error dolorum sed recusandae recusandae est. Et tenetur perferendis sequi itaque similique. Porro facere qui saepe alias. Qui itaque corporis explicabo itaque. Quibusdam vel expedita odio quaerat libero veniam praesentium minus.</p>
          </li>
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          <li>
            <h2>Ab recusandae minima commodi sed pariatur.</h2>
            <p>Velit in voluptatum quia consequatur fuga et repellendus ut cupiditate. Repudiandae dignissimos dolores qui. Possimus nihil laboriosam enim dolorem vitae accusantium accusamus dolor. Tenetur fuga omnis et est accusantium dolores. Possimus vitae aliquid. Vitae commodi et autem vitae rerum.</p>
          </li>
          <li>
            <h2>Voluptatem ipsa sed illum numquam aliquam sint.</h2>
            <p>Suscipit quis error dolorum sed recusandae recusandae est. Et tenetur perferendis sequi itaque similique. Porro facere qui saepe alias. Qui itaque corporis explicabo itaque. Quibusdam vel expedita odio quaerat libero veniam praesentium minus.</p>
          </li>
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          <li>
            <h2>Ab recusandae minima commodi sed pariatur.</h2>
            <p>Velit in voluptatum quia consequatur fuga et repellendus ut cupiditate. Repudiandae dignissimos dolores qui. Possimus nihil laboriosam enim dolorem vitae accusantium accusamus dolor. Tenetur fuga omnis et est accusantium dolores. Possimus vitae aliquid. Vitae commodi et autem vitae rerum.</p>
          </li>
          <li>
            <h2>Voluptatem ipsa sed illum numquam aliquam sint.</h2>
            <p>Suscipit quis error dolorum sed recusandae recusandae est. Et tenetur perferendis sequi itaque similique. Porro facere qui saepe alias. Qui itaque corporis explicabo itaque. Quibusdam vel expedita odio quaerat libero veniam praesentium minus.</p>
          </li>
        </ul>
      </div>
    `;
  },
});

export default Index;

Muy simple. Aquí, hemos agregado una clase index al <div></div> existente y adentro, hemos agregado un <ul></ul> (lista desordenada) con una clase cards . En el interior, hemos añadido 12 <li></li> etiquetas, cada una de las cuales representa una "tarjeta" con algo de contenido de lorem ipsum. Aunque la longitud es técnicamente arbitraria, para dar sentido a lo que implementaremos a continuación, tiene sentido tener varios elementos en lugar de 1 o 2 (siéntete libre de jugar con la longitud, ya que nuestro código seguirá funcionando ).

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  css: `
    .cards {
      opacity: 0;
      border-top: 1px solid #eee;
      border-bottom: 1px solid #eee;
      padding: 40px;
      overflow-x: scroll;
      display: flex;
    }

    .cards li {
      background: #fff;
      border: 1px solid #eee;
      box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
      padding: 30px;
      border-radius: 3px;
      list-style: none;
      width: 300px;
      min-width: 300px;
    }

    .cards li h2 {
      font-size: 28px;
      line-height: 36px;
      margin: 0;
    }

    .cards li p {
      font-size: 16px;
      line-height: 24px;
      color: #888;
    }

    .cards li:not(:last-child) {
      margin-right: 30px;
    }
  `,
  render: () => {
    return `
      <div class="index">
        <ul class="cards">
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          ...
        </ul>
      </div>
    `;
  },
});

export default Index;

Justo encima de nuestro render método, hemos agregado una propiedad a nuestro componente css que, como era de esperar, nos permite agregar algo de estilo CSS a nuestro componente. Lo que logran estos estilos es darnos una lista desplazada horizontalmente de "tarjetas" que se extienden más allá del borde del navegador, como esta:

Ahora que tenemos nuestros estilos base y marcado en el navegador, a continuación, queremos agregar el JavaScript necesario para cambiar dinámicamente la primera tarjeta en la lista para comenzar en el medio de la página. Nuestro objetivo es imitar un diseño como la lista de novedades del diseño actual de Apple Store:

Para hacerlo, vamos a conectar el JavaScript necesario como método en nuestro componente Joystick.

Configuración dinámica del relleno en la carga de la página

Antes de manejar la parte de "carga en la página" aquí, primero, debemos escribir JavaScript para seleccionar nuestra lista en el DOM, calcular el punto central actual de la ventana y luego establecer el relleno del lado izquierdo de nuestra lista. Así es como lo hacemos:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  state: {
    defaultListPadding: '20px',
  },
  methods: {
    handleSetListPadding: (component = {}) => {
      const list = component.DOMNode.querySelector('ul.cards');
      const windowCenterPoint = window.innerWidth / 2;
      
      if (list) {
        list.style.paddingLeft = windowCenterPoint >= 400 ? `${windowCenterPoint}px` : component.state.defaultListPadding;
        list.style.opacity = 1;
      }
    },
  },
  css: `...`,
  render: () => {
    return `
      <div class="index">
        <ul class="cards">
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          ...
        </ul>
      </div>
    `;
  },
});

export default Index;

En un componente Joystick, un "método" (definido como una función de método en el methods propiedad de la opción de nuestro componente) es una función miscelánea en nuestro componente que se puede llamar desde cualquier parte del componente. Aquí, hemos definido handleSetListPadding como un método para que podamos llamarlo cuando nuestro componente se monte en la pantalla (más sobre esto en un momento).

Para comenzar, agregamos un argumento como component que Joystick nos entrega automáticamente (el marco asigna automáticamente el último argumento posible en una función para que sea la instancia del componente, ya que no tenemos ningún argumento, por defecto es la primera ranura). En ese component objeto de instancia, se nos da un DOMNode propiedad que representa el nodo DOM representado para nuestro componente (en este caso, el Index componente que estamos creando) en el navegador.

A partir de eso, podemos usar la selección de DOM de JavaScript estándar y aquí, lo hacemos usando el .querySelector() método en ese nodo DOM para localizar nuestro ul.cards list, almacenándolo en una variable list .

A continuación, debido a que queremos configurar el relleno del lado izquierdo de esa lista para que sea el centro de la ventana, debemos calcular cuál es el valor de píxel de ese punto central. Para hacerlo, podemos tomar el window.innerWidth valor y dividirlo por 2 (por ejemplo, si nuestra ventana actualmente es 1000 píxeles de ancho, windowCenterPoint se convertiría en 500 ).

Con nuestro list y windowCenterPoint asumiendo que hicimos encuentra un list elemento en la página, queremos modificar el list.style.paddingLeft valor, estableciéndolo igual a un valor de cadena, concatenando el valor de windowCenterPoint con px (Hacemos esto porque el valor que obtenemos es un número entero pero necesitamos establecer nuestro relleno como un valor de píxel).

Note que aquí, hacemos esto paddingLeft valor condicional basado en el valor de windowCenterPoint . Si el valor es mayor que 400 , queremos configurarlo como paddingLeft . Si es no , queremos recurrir a un valor de relleno predeterminado (esto garantiza que no saquemos accidentalmente las tarjetas completamente de la pantalla para ventanas de visualización más pequeñas). Para almacenar este valor predeterminado, hemos agregado el state propiedad a las opciones de nuestro componente, que es un objeto que contiene valores predeterminados para el estado de nuestro componente. Aquí, hemos asignado defaultListPadding a una cadena '20px' que usamos como "else" en nuestro windowCenterPoint >= 400 ternario.

A continuación, justo debajo de nuestra llamada para configurar list.style.paddingLeft también nos aseguramos de configurar list.style.opacity a 1. ¿Por qué? Bueno, en nuestro css que configuramos anteriormente, configuramos nuestra lista en opacity: 0; por defecto. Este es un "truco" para evitar que nuestra lista salte visualmente en la página durante una carga lenta de la página (acertar o fallar dependiendo de la velocidad de la conexión). Esto elimina la posibilidad de que se produzca un error visual que sería molesto para el usuario.

Si bien tenemos nuestro código escrito, esto actualmente no hará nada. Para que funcione, necesitamos llamar a nuestro método.

Llamar a handleSetListPadding en el montaje y cambiar el tamaño de la ventana

Esta parte es bastante simple, aquí está el código para hacerlo:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  state: {
    defaultListPadding: '20px',
  },
  lifecycle: {
    onMount: (component = {}) => {
      component.methods.handleSetListPadding();

      window.addEventListener('resize', () => {
        component.methods.handleSetListPadding();
      });
    },
  },
  methods: {
    handleSetListPadding: (component = {}) => {
      const list = component.DOMNode.querySelector('ul.cards');
      const windowCenterPoint = window.innerWidth / 2;
      
      if (list) {
        list.style.paddingLeft = windowCenterPoint >= 400 ? `${windowCenterPoint}px` : component.state.defaultListPadding;
        list.style.opacity = 1;
      }
    },
  },
  css: `...`,
  render: () => {
    return `
      <div class="index">
        <ul class="cards">
          <li>
            <h2>Aliquam impedit ipsa adipisci et quae repellat sit.</h2>
            <p>Deleniti quibusdam quia assumenda omnis. Rerum cum et error vero enim ex. Sapiente est est ut omnis possimus temporibus in.</p>
          </li>
          ...
        </ul>
      </div>
    `;
  },
});

export default Index;

Agregando una opción más a nuestro componente lifecycle , al objeto que se le pasa le asignamos una propiedad onMount que está configurado para una función que Joystick llamará tan pronto como el HTML de nuestro componente se represente en el navegador. Al igual que con nuestro handleSetListPadding método, Joystick pasa automáticamente el component instancia a todos los métodos de ciclo de vida disponibles.

Aquí, usamos ese component instancia para acceder a nuestro handleSetListPadding método, llamándolo con component.methods.handleSetListPadding() . Además de esto, también debemos considerar el cambio de tamaño del navegador por parte del usuario y cómo esto afectará el punto central de la ventana. Todo lo que tenemos que hacer es agregar un detector de eventos en el window para el resize evento y en la devolución de llamada que se llama cuando se detecta ese evento, otra llamada a component.methods.handleSetListPadding() .

Esto funciona porque estamos recuperando el valor de window.innerWidth en el momento de la llamada para el handleSetListPadding función. Aquí, entonces, porque estamos obteniendo ese valor después se ha producido el cambio de tamaño, podemos confiar en que window.innerWidth contendrá el ancho actual y no el ancho que teníamos al cargar la página.

¡Eso es todo! Ahora, si cargamos nuestra página en el navegador, deberíamos poder cambiar el tamaño y ver que nuestra primera tarjeta cambia su borde izquierdo para alinearse con el centro de la ventana.

Terminando

En este tutorial, aprendimos a manipular el DOM dinámicamente con JavaScript. Aprendimos cómo posicionar dinámicamente un elemento a través de su CSS usando el DOM style propiedad en un elemento de lista. También aprendimos a confiar en el window cambiar el tamaño del evento para volver a calcular el punto central de nuestro navegador cada vez que cambia el ancho del navegador.