- Aprender
- Informática
- Programación
- Programación en JavaScript
- Detectores de eventos
Detectores de eventos: páginas interactivas
PrincipianteUnidad 6 de 9 del curso Curso de Programación en JavaScript
Lo que aprenderás
- Haz que una página reaccione cuando ocurra algo: registra detectores en elementos, lee la información del objeto de evento y decide cuánto tiempo permanece activo cada detector.
- Registra un detector de eventos en un elementoUsa
addEventListenerpara ejecutar una función cada vez que ocurra un clic, una pulsación de tecla, una acción de formulario o un evento de página en un elemento. - Lee la información del objeto de eventoUsa el objeto de evento que recibe un detector para saber qué ocurrió y dónde: su tipo, destino, destino actual y detalles específicos del evento.
- Elimina detectores de eventos y controla cómo se ejecutanDeja de escuchar cuando ya no necesites un detector, usa las opciones
once,signalypassive, y dispara tus propios eventos personalizados.
- Registra un detector de eventos en un elemento
- Explica cómo un evento desciende hasta su destino y vuelve a subir por la página, cancela el comportamiento integrado del navegador cuando lo sustituyas y usa la propagación por burbujeo para manejar varios elementos con un solo detector.
- Explica la propagación por burbujeo y la capturaDescribe las tres fases de la propagación de eventos y predice el orden en que se ejecutan los detectores de elementos anidados.
- Evita las acciones predeterminadas y detén la propagaciónCancela la respuesta integrada del navegador a un evento cuando tu código la sustituya y detén el recorrido de un evento solo cuando sea realmente necesario.
- Maneja eventos de varios elementos mediante la delegación de eventosUsa un detector en un nodo antecesor compartido y
event.target.closest()para manejar eventos de varios elementos hijos, incluidos los añadidos posteriormente.
- Explica la propagación por burbujeo y la captura
- Responde a pulsaciones de teclas, cambios de foco, clics, toques y entrada de lápiz, y construye una interacción de arrastre que funcione con cualquier tipo de puntero.
- Responde a pulsaciones de teclas y cambios de focoUsa
keydown,keyupy los eventos de foco, elige entreevent.keyyevent.code, y construye atajos de teclado que no estorben al usuario. - Maneja clics, toques y entrada de lápiz con eventos de punteroUsa
clickpara la activación y los eventos de puntero para pulsar, mover y soltar con un ratón, un dedo o un lápiz; lee coordenadas y botones y elige los eventos adecuados para el paso del puntero. - Construye una interacción de arrastre con eventos de punteroCombina
pointerdown,pointermoveypointerupcon la captura del puntero para mover un elemento, haz que funcione en pantallas táctiles y ofrece una forma de hacer lo mismo sin arrastrar.
- Responde a pulsaciones de teclas y cambios de foco
- Lee lo que introducen los usuarios mientras escriben, maneja el envío de formularios en JavaScript y comprueba los datos de entrada con las funciones de validación del navegador y tus propias reglas, con mensajes de error claros.
- Lee campos de formulario y responde mientras los usuarios escribenLee campos de texto, números, casillas de verificación, botones de opción y listas desplegables, usa los eventos
inputychangeen los momentos adecuados y recopila un formulario completo conFormData. - Maneja el envío de formularios en JavaScriptEscucha el evento
submitdel formulario, evita que la página se vuelva a cargar, lee los datos enviados y conoce qué desencadena un envío. - Usa la validación integrada del navegadorDescribe reglas con atributos HTML, lee el resultado en el objeto
validity, comprueba campos desde el código y aplica estilos a los campos no válidos en el momento adecuado. - Escribe validaciones personalizadas con mensajes de error clarosAñade reglas que los atributos HTML no pueden expresar, escribe mensajes útiles, muéstralos en el momento adecuado y conéctalos con sus campos.
- Lee campos de formulario y responde mientras los usuarios escriben
- Crea elementos interactivos que funcionen con ratón, teclado y lector de pantalla: prioriza el HTML nativo, añade roles, compatibilidad con teclado y estado cuando construyas tus propios controles, y mantén el foco donde lo esperan los usuarios.
- Prioriza los elementos nativos y permite que los controles personalizados reciban el focoConoce qué proporcionan los elementos interactivos nativos y qué debe añadir un control personalizado construido a partir de un
<div>o un<span>: un rol, la capacidad de recibir el foco, un nombre accesible y un indicador de foco visible. - Añade compatibilidad con el teclado y estado a los controles personalizadosHaz que los botones personalizados, botones de alternancia, controles de despliegue, casillas de verificación y grupos de elementos funcionen con el teclado, y mantén su estado ARIA en consonancia con lo que ven los usuarios.
- Mantén el foco donde lo esperan los usuarios cuando cambie la páginaMueve el foco de forma deliberada cuando se abra, cierre o elimine contenido o se muestren errores, y nunca lo muevas de forma inesperada mientras los usuarios escriben o eligen.
- Prioriza los elementos nativos y permite que los controles personalizados reciban el foco
Guía de estudio de Detectores de eventos
1Responde a eventos con detectores de eventos
Las páginas web responden mediante funciones registradas para eventos. La configuración elige el elemento y el tipo de evento; cada vez que ocurre después, llega un objeto de evento que describe lo sucedido. Leer ese objeto permite que un detector actúe sobre el control adecuado, incluso cuando un clic cae en un icono dentro de él. Las referencias y opciones de los detectores permiten mantener activas las respuestas de una función exactamente el tiempo necesario, y los eventos personalizados permiten que una parte de la página avise a otra.
1.1 Los detectores convierten acciones en código
Una página puede esperar a que una persona actúe. Un evento informa de algo que ocurrió y un detector de eventos es la función que se llama en respuesta. Registrar un detector configura esa respuesta; no realiza la acción inmediatamente.
Añade la función al elemento
Selecciona un elemento que ya exista, elige el nombre exacto del evento y pasa una función:
const button = document.querySelector("#like");
let likes = 0;
button.addEventListener("click", function () {
likes = likes + 1;
button.textContent = "Likes: " + likes;
});
La selección, la configuración de la variable y el registro se ejecutan cuando se ejecuta el script. Cada clic posterior ejecuta el cuerpo de la función. Después de tres clics, el botón muestra «Likes: 3». Mantener likes fuera de la función conserva el total entre clics; poner let likes = 0 dentro lo reiniciaría cada vez.
Pasa una referencia a la función, como showMenu, sin paréntesis. En button.addEventListener("click", showMenu()), JavaScript llama a showMenu inmediatamente y pasa su valor de retorno como detector. Para proporcionar un argumento más tarde, envuelve la llamada: button.addEventListener("click", () => showMenu("main"));.
Elige un evento que realmente ocurra
Los nombres son cadenas que distinguen mayúsculas de minúsculas y no llevan el prefijo «on». Usa "click", no "Click" ni "onclick". Se acepta un nombre inventado sin error, pero el navegador no lo emitirá para un clic. Entre las familias habituales están los eventos de puntero como pointerdown y pointerup, los eventos de teclado como keydown, los eventos de formulario como input y submit, y los eventos de foco como focusin. DOMContentLoaded tiene su propia combinación exacta de mayúsculas y minúsculas.
Configura el elemento adecuado en el momento adecuado
Un script en la cabecera puede ejecutarse antes de que exista un botón. La selección devuelve entonces null, y llamar a addEventListener sobre él genera un error. Carga un script externo con defer, colócalo después del marcado o registra la configuración en el evento DOMContentLoaded del documento. Un elemento creado posteriormente necesita configuración después de su creación o un detector en un nodo antecesor.
querySelectorAll devuelve una colección de nodos (NodeList), no un solo destino de evento. Registra detectores en sus elementos:
document.querySelectorAll(".tab").forEach((tab) => {
tab.addEventListener("click", selectTab);
});
Permite que coexistan los detectores
| Método | Qué hace el registro |
|---|---|
addEventListener("click", fn) | Añade un detector junto a otros y acepta opciones. |
button.onclick = fn | Sustituye el único manejador de la propiedad; asigna null para eliminarlo. |
Un atributo HTML onclick | Coloca JavaScript dentro del marcado. |
Los detectores con la misma configuración de captura se ejecutan en orden de registro. En el destino del evento, los detectores de captura se ejecutan antes que los que no son de captura. Volver a añadir el mismo objeto de función con el mismo tipo y configuración de captura no añade una segunda copia.
Puntos clave
- Pasa la propia función; usa una función envolvente cuando la llamada posterior necesite argumentos.
- Los nombres de eventos requieren una escritura exacta, incluidas las mayúsculas y minúsculas.
- Un elemento debe existir cuando se registra su detector.
- Recorre una NodeList para registrar detectores en sus elementos.
addEventListenerpermite varios detectores; una propiedad de manejo de eventos contiene uno.
1.2 Identifica dónde comenzó un evento
El navegador pasa un objeto de evento a tu detector. Sus propiedades explican el evento que desencadenó esa llamada concreta. Llama al parámetro event o e; tú eliges el nombre.
button.addEventListener("click", (event) => {
console.log(event.type);
console.log(event.currentTarget);
});
Esto registra "click" y el botón durante la llamada al detector. Registrar el objeto completo puede ayudarte a descubrir sus otras propiedades.
Distingue el origen del detector
Imagina un botón que contiene una imagen:
<button id="save" data-id="7">
<img src="disk.svg" alt=""> Save
</button>
| Propiedad | Significado | Al hacer clic en la imagen |
|---|---|---|
event.target | Dónde comenzó el evento | La imagen |
event.currentTarget | Dónde se registró el detector que se está ejecutando | El botón, para un detector del botón |
Al hacer clic en el texto «Save», el botón también es el destino. El texto forma parte del botón; no se convierte en un elemento de destino independiente.
Esta diferencia importa al leer datos. La imagen no tiene el data-id del botón, por lo que event.target.dataset.id es undefined al hacer clic en la imagen. Un detector registrado directamente en el botón puede leer event.currentTarget.dataset.id. Un detector en un contenedor más grande puede encontrar el botón deseado con event.target.closest("button") y comprobar que existe una coincidencia.
Lee los detalles de este evento
Todo evento tiene un type, un target y un currentTarget. El booleano bubbles indica si se propaga de vuelta hacia arriba por los nodos antecesores. cancelable indica si el código puede cancelar su acción predeterminada y defaultPrevented registra si se ha producido esa cancelación. timeStamp registra el momento de creación del evento en milisegundos con respecto al origen temporal de la página.
Las distintas familias de eventos añaden detalles útiles. Un click moderno proporciona un PointerEvent, que hereda propiedades de eventos de ratón como clientX, clientY y button. Un KeyboardEvent añade key, code e indicadores de teclas modificadoras. Un detector de input puede usar event.target.value para leer el nuevo texto del campo.
Para un detector de galería, event.target.tagName podría ser "IMG" al hacer clic en una foto o "DIV" al hacer clic en el espacio entre fotos. Maneja el caso que realmente se indique. No supongas que todos los eventos dentro de un contenedor provienen del mismo tipo de hijo.
Usa el valor mientras se ejecuta el detector
En un detector con una función normal, this equivale a event.currentTarget. Una función flecha toma this del código que la rodea; no recibe el elemento como this. Usar event.currentTarget funciona con ambos estilos de función.
Después del despacho, currentTarget se restablece a null. Una consola puede mostrar ese valor posterior cuando expandes un evento registrado anteriormente. Registra event.currentTarget directamente dentro del detector para inspeccionar el elemento durante la llamada.
Puntos clave
targetidentifica el origen del evento;currentTargetidentifica el elemento del detector que se está ejecutando.- Lee los datos del elemento que realmente los contiene.
- Las familias de eventos añaden propiedades como coordenadas, valores de teclas y modificadores.
currentTargetestá disponible durante la ejecución del detector y después pasa a sernull.
1.3 Controla la duración de un detector
Un detector debe permanecer activo mientras lo necesite la función a la que sirve. Para una interacción temporal con el teclado, conserva la referencia a la función para poder eliminarla cuando se cierre la interacción.
Elimina la misma función
function onKey(event) {
console.log(event.key);
}
document.addEventListener("keydown", onKey);
// When the feature closes:
document.removeEventListener("keydown", onKey);
La eliminación requiere que coincidan el tipo de evento, el objeto de función y la configuración de captura. Una función anónima también puede eliminarse si se guarda su referencia, como en const handler = () => save();. Escribir una nueva () => save() en la llamada de eliminación crea una función distinta, aunque el código parezca idéntico.
Elige las opciones de registro
| Opción | Efecto |
|---|---|
once: true | Elimina este detector después de su primera llamada. |
signal: controller.signal | Elimina el detector cuando se cancela ese controlador. |
capture: true | Escucha durante el recorrido de captura. |
passive: true | Indica que el detector no cancelará la acción predeterminada. |
Un aviso de bienvenida de una sola ejecución puede usar { once: true }. Una función con varios detectores puede darles una misma señal:
const controller = new AbortController();
window.addEventListener("pointermove", moveCursor, {
signal: controller.signal
});
window.addEventListener("keydown", onKey, {
signal: controller.signal
});
// Close the feature:
controller.abort();
La cancelación elimina ambos registros. Un controlador cancelado no se puede restablecer; crea uno nuevo para un nuevo periodo de actividad. Para eliminar con removeEventListener, repite capture: true si se usó en el registro. once, signal y passive no forman parte de los criterios de coincidencia para la eliminación.
La llamada a preventDefault() de un detector pasivo se ignora. Los navegadores suelen hacer pasivos por defecto ciertos detectores relacionados con el desplazamiento en destinos a nivel de documento, incluidos wheel, touchstart y touchmove; usa ahí { passive: false } de forma explícita cuando sea necesario cancelar la acción.
Evita la acumulación de funciones de devolución de llamada
Si cada clic en «Iniciar» añade un nuevo detector anónimo al tablero, iniciar dos veces hace que un clic en el tablero ejecute dos funciones de devolución de llamada. Registra el detector del tablero una vez, reutiliza el mismo objeto de función o cancela el grupo anterior antes de sustituirlo. Las distintas funciones de devolución de llamada con once aún pueden acumularse antes de ejecutarse.
Desconectar un elemento conserva sus detectores. Funcionan si se vuelve a insertar y dispatchEvent() puede llegar a ellos mientras está desconectado. El elemento puede ser recogido por el recolector de basura cuando ya no se puede acceder a él ni a sus detectores. Un detector de window o document permanece registrado hasta que se elimine explícitamente.
Envía un evento personalizado
const event = new CustomEvent("cart:add", {
detail: { id: 42 },
bubbles: true
});
item.dispatchEvent(event);
Los detectores leen event.detail.id. El despacho llama a los detectores inmediatamente, antes de terminar. bubbles: true permite que los nodos antecesores también lo reciban. Un evento enviado con dispatchEvent() tiene isTrusted igual a false; se puede confiar en los eventos despachados por el navegador incluso cuando los haya provocado una API como focus().
Puntos clave
- La eliminación requiere que coincidan el tipo, la identidad de la función y la configuración de captura.
oncetermina un registro después de una llamada; cancelar una señal termina sus registros asociados.- Un detector pasivo no puede cancelar una acción predeterminada.
- Las nuevas funciones de devolución de llamada añadidas en cada configuración pueden acumularse.
- Desconectar un elemento conserva sus detectores.
- Los eventos personalizados transportan datos en
detaily se despachan de forma síncrona.
2Sigue los eventos en su recorrido por la página
Los elementos anidados comparten una ruta de eventos. Un clic puede pasar por un contenedor antes de llegar a su destino y visitarlo de nuevo durante el burbujeo, por lo que un solo detector basta para una lista cambiante. Seguir esa ruta ayuda a explicar por qué una acción en un hijo también activa una en el padre. Distingue la cancelación del comportamiento del navegador del control de la propagación y después usa el origen del evento para elegir qué acciones debe manejar un detector compartido.
2.1 La ruta de un evento por elementos anidados
Un clic dentro de un botón también puede ser recibido por los nodos antecesores del botón. La propagación es el recorrido del evento por el árbol del documento. Seguir ese recorrido explica tanto los útiles detectores compartidos como las acciones adicionales inesperadas.
Sigue tres fases
- Captura: el evento desciende desde
windowpor los nodos antecesores hacia el destino. - Destino: los detectores se ejecutan en el elemento donde comenzó el evento.
- Burbujeo: un evento que se propaga por burbujeo vuelve a subir por los nodos antecesores hasta
window.
La propiedad eventPhase indica 1 para captura, 2 en el destino y 3 para burbujeo. Un detector registrado sin opciones se ejecuta en el destino o en el recorrido de burbujeo. Un detector con { capture: true } se ejecuta en el recorrido descendente, incluido el destino si se registró allí.
Sigue un clic concreto
<div id="outer">
<div id="inner">
<button>Go</button>
</div>
</div>
Supón que el botón y cada div tienen detectores de clic normales que registran sus nombres. Al hacer clic en «Go» se registra «button», «inner» y después «outer». Al hacer clic en una zona vacía de inner se registra «inner» y después «outer»; el botón no está en la ruta de ese evento.
Ahora añade un detector de captura a outer. Un clic en el botón registra primero «outer (capture)» y después «button», «inner» y «outer» de los detectores normales. El detector de captura recibe el clic antes que los descendientes. Después de outer, el evento puede continuar por body, html, document y window si hay detectores registrados allí.
Es un solo evento, no un nuevo clic creado en cada nodo antecesor. target sigue siendo el botón durante todo el recorrido. currentTarget cambia a medida que se ejecuta cada detector. En el detector exterior, leer target sigue identificando el botón original.
Comprueba qué eventos se propagan por burbujeo
| Se propagan por burbujeo | No se propagan por burbujeo |
|---|---|
click, input, change, submit | invalid |
keydown, pointerdown | focus, blur |
focusin, focusout | pointerenter, pointerleave |
pointerover, pointerout | mouseenter, mouseleave |
La propiedad bubbles del evento indica si tiene una fase de burbujeo. Los eventos sin burbujeo todavía pueden ser observados por un detector de captura de un nodo antecesor. Para manejar los cambios de foco mediante un detector normal en un contenedor, elige focusin o focusout.
Explica un error de acciones anidadas
Una tarjeta de producto abre una vista de detalles cuando se hace clic en ella. Su botón «Eliminar» elimina el producto. Al hacer clic en ese botón primero se elimina el producto, después el evento llega al detector de la tarjeta y abre la vista de detalles. La ruta del evento explica por qué se ejecutan ambas funciones de devolución de llamada.
La tarjeta puede ignorar los clics de eliminación con if (event.target.closest(".remove")) return;. Como alternativa, el botón puede detener la propagación. Filtrar en la tarjeta conserva el evento para otros nodos antecesores que puedan necesitarlo.
Puntos clave
- La propagación sigue las fases de captura, destino y después burbujeo para los eventos que se propagan por burbujeo.
- Los detectores normales de nodos antecesores reciben los clics de los descendientes durante el recorrido ascendente.
targetpermanece fijo mientrascurrentTargetcambia.- Usa un evento de foco que se propague por burbujeo o la captura para observar el foco desde un nodo antecesor.
2.2 Cancela una acción o detén un recorrido
Un detector puede cambiar lo que ocurre después de un evento o por dónde se propaga. Son decisiones distintas. Un evento de enlace puede seguir propagándose por burbujeo incluso después de cancelar la navegación.
Sustituye una acción del navegador
Una acción predeterminada es un comportamiento que proporciona el navegador: seguir un enlace, enviar un formulario, alternar una casilla de verificación, insertar texto, desplazar la página con la barra espaciadora o mover el foco con Tab. Usa preventDefault() cuando tu código sustituya esa acción:
help.addEventListener("click", (event) => {
event.preventDefault();
openHelpPanel();
});
Para un enlace a /help, esto abre el panel sin navegar. El enlace sigue ofreciendo su destino cuando JavaScript no se ejecuta.
Solo se pueden cancelar los eventos cancelables. Examina event.cancelable; defaultPrevented registra si la cancelación se realizó correctamente. input ocurre después de que cambie el valor y no es cancelable. Llamar a preventDefault() ahí no elimina el texto insertado. Un evento beforeinput cancelable es el lugar adecuado para interceptar una edición; cancelar un keydown pertinente puede impedir el efecto predeterminado de esa tecla.
Controla la propagación por separado
| Método | ¿Detiene la acción predeterminada del navegador? | ¿Detiene los detectores posteriores? |
|---|---|---|
preventDefault() | Sí, si el evento es cancelable y el detector no es pasivo | No |
stopPropagation() | No | Detiene el recorrido hacia otros elementos; los detectores de este elemento siguen ejecutándose |
stopImmediatePropagation() | No | También detiene los detectores restantes de este elemento |
Un enlace cuyo detector solo detiene la propagación sigue navegando a su URL. Cancelar solo la navegación sigue permitiendo que un detector de clic del documento reciba el evento. Devolver false desde una función de devolución de llamada de addEventListener no hace ninguna de las dos cosas. Una propiedad o un atributo de manejo de eventos puede cancelar su acción predeterminada devolviendo false, pero tampoco detiene la propagación.
Mantén el funcionamiento compartido
Un nodo antecesor puede cerrar un menú o contar interacciones. Detener la propagación oculta el evento a ese código. A menudo, el nodo antecesor puede examinar target o defaultPrevented.
Por ejemplo, un menú se cierra solo cuando un clic cae fuera tanto del menú como de su control de apertura:
document.addEventListener("click", (event) => {
if (!menu.contains(event.target) &&
!menuButton.contains(event.target)) {
menu.hidden = true;
}
});
contains() incluye el propio elemento y sus descendientes. Comprobar el control de apertura es importante porque el clic que abre el menú también se propaga por burbujeo hasta el documento. Sin esa segunda comprobación, el menú recién abierto se cerraría inmediatamente.
Cancela solo una acción que manejes
En un detector de teclado, comprueba la tecla antes de cancelar. Cancelar todas las teclas bloquea la escritura normal y puede impedir que Tab mueva el foco. En un detector pasivo, la cancelación se ignora por completo.
Puntos clave
preventDefault()cancela una acción predeterminada cuando es posible, sin detener la propagación.stopPropagation()detiene el recorrido, pero no la acción predeterminada.stopImmediatePropagation()también omite los detectores restantes del elemento actual.- Cuando ocurre
input, ya es demasiado tarde para cancelar una edición. - Las comprobaciones de clic fuera deben incluir tanto el elemento emergente como su control de apertura.
- Cancela solo las acciones de teclado específicas que maneja tu código.
2.3 Un detector para una lista cambiante
La delegación de eventos sitúa un detector en un nodo antecesor estable y usa el origen del evento para elegir una acción. Es útil cuando una lista tiene muchos elementos o cuando el código crea elementos nuevos después de la configuración.
Encuentra el control deseado
Un clic que se propaga por burbujeo llega a la lista independientemente de cuál de sus descendientes se haya pulsado. Parte de event.target, encuentra el control coincidente más cercano y descarta los clics que no correspondan:
const list = document.querySelector("#todos");
list.addEventListener("click", (event) => {
const button = event.target.closest(".remove");
if (!button || !list.contains(button)) return;
const item = button.closest("li");
console.log("Removing item " + item.dataset.id);
item.remove();
});
La lista contiene elementos como <li data-id="7">Buy milk <button class="remove">Remove</button></li>. El detector pertenece a la lista, por lo que un elemento nuevo con el mismo marcado funciona inmediatamente después de insertarse.
Sigue en el código un clic en un icono. El destino es el icono, closest(".remove") encuentra su botón y button.closest("li") encuentra el elemento de lista. Su data-id identifica el elemento en el mensaje. Por último, remove() modifica la página.
Conserva las dos comprobaciones
| Comprobación | Por qué importa |
|---|---|
!button | Un clic en un espacio de la lista o en el texto normal de un elemento puede no encontrar ningún botón de eliminación. |
!list.contains(button) | closest() puede subir más allá de la lista y encontrar un nodo antecesor exterior que coincida. |
Comprobar solo event.target.matches(".remove") deja fuera los clics en iconos porque el propio icono no coincide. closest() comprueba el destino y sube por sus nodos antecesores. En un detector delegado, currentTarget es la lista; no se convierte en el botón encontrado.
Dirige varias acciones
Los atributos de datos permiten que un detector de barra de herramientas elija entre acciones conocidas:
const actions = { bold: makeBold, italic: makeItalic,
clear: clearText };
toolbar.addEventListener("click", (event) => {
const button = event.target.closest("button[data-action]");
if (!button || !toolbar.contains(button)) return;
const action = actions[button.dataset.action];
if (typeof action === "function") action();
});
Un botón con data-action="clear" selecciona clearText. La comprobación de la función maneja una acción desconocida sin intentar llamar a undefined.
Elige un evento adecuado
La delegación funciona con eventos que se propagan por burbujeo, además de los clics. Un solo detector de input en un formulario puede pasar event.target a una función que compruebe campos. Para delegar cambios de foco, usa focusin o focusout; los eventos normales focus y blur no se propagan por burbujeo. Para las transiciones al pasar el puntero, usa eventos over/out que se propaguen por burbujeo cuando necesites delegación.
Una llamada a stopPropagation() de un descendiente puede impedir que un evento llegue al contenedor. Cuando un detector delegado parezca inactivo, comprueba el tipo de evento, el destino, la coincidencia con el selector y si algo debajo del contenedor detuvo el recorrido.
Puntos clave
- Registra el detector en un nodo antecesor que permanezca aunque cambien sus hijos.
closest()encuentra un control incluso cuando se hace clic en un icono anidado.- Descarta los resultados sin coincidencia y las coincidencias fuera del contenedor.
- La delegación incluye automáticamente los hijos que coincidan y se añadan después.
- Los atributos de datos pueden dirigir un detector compartido hacia acciones conocidas.
- La delegación necesita un evento que llegue al nodo antecesor.
3Maneja la entrada del teclado y del puntero
Una interacción puede llegar mediante teclas, un ratón, un dedo o un lápiz. El evento que elijas determina si recibes una activación, un valor de texto modificado o el movimiento de un puntero concreto. Los sistemas de coordenadas y las posiciones iniciales almacenadas hacen que el arrastre sea predecible. El foco y los controles alternativos permiten usar la misma página cuando una persona navega con el teclado o no puede realizar un gesto de arrastre.
3.1 Teclas, texto y foco
La entrada del teclado va al elemento que tiene el foco, disponible como document.activeElement, y después se propaga por burbujeo. Por tanto, un detector en el documento puede recibir teclas tanto de campos como de otros controles. Ese alcance hace útiles los atajos de teclado, pero también significa que pueden interrumpir la escritura.
Elige la propiedad por su significado
| Propiedad | Indica | Ejemplo |
|---|---|---|
key | El valor producido, con la distribución y los modificadores | "a", "A", "Enter", " " |
code | La posición física de la tecla | "KeyA", "Enter", "Space" |
En un teclado AZERTY, la posición marcada con Q en un teclado estadounidense produce key "a" y code "KeyQ". Usa key para el significado, como Escape para cerrar o una letra como atajo. Usa code para posiciones fijas, como las teclas de movimiento en un juego. El key de la barra espaciadora es un solo espacio, no "Space".
keydown se dispara en la pulsación inicial y puede repetirse mientras se mantiene pulsada la tecla. Las llamadas repetidas tienen event.repeat === true. keyup se dispara al soltarla. Usa keydown en lugar del evento obsoleto keypress.
Maneja un atajo con precisión
document.addEventListener("keydown", (event) => {
if ((event.ctrlKey || event.metaKey) &&
event.key.toLowerCase() === "s") {
event.preventDefault();
saveDraft();
}
});
Esto maneja Ctrl+S y Comando+S. Convertir a minúsculas permite aceptar también "S". La cancelación sustituye la acción de guardar página del navegador solo para el atajo que coincide. Otros indicadores de teclas modificadoras son shiftKey y altKey.
Para un atajo de una sola letra en toda la página, omite los contextos de escritura antes de actuar:
if (event.target.matches("input, textarea, select") ||
event.target.isContentEditable) return;
if (event.repeat) return;
La primera comprobación protege la entrada de texto. La segunda hace que una acción se ejecute una vez por cada pulsación física. No canceles Tab en toda la página; es la forma en que avanzan los usuarios de teclado.
Escucha el texto después de que cambie
Para una tecla normal que produce un carácter, la secuencia es keydown, beforeinput, input y después keyup. Leer value en keydown da el valor anterior a la inserción de ese carácter. Usa input para una vista previa del texto o un contador de caracteres. También maneja ediciones como pegar y dictar, que no necesitan una pulsación de tecla correspondiente.
Observa los cambios de foco
focus y blur indican que se recibe o se pierde el foco sin propagarse por burbujeo. focusin y focusout sí se propagan, por lo que un formulario puede observar sus campos mediante un solo detector. relatedTarget identifica el otro elemento implicado en el cambio, o null cuando no hay ninguno.
password.addEventListener("focus", () => {
hint.hidden = false;
});
password.addEventListener("blur", () => {
hint.hidden = true;
});
La indicación está disponible cuando el campo recibe el foco, también mediante Tab. Llamar a element.focus() mueve el foco desde el código; la siguiente entrada del teclado va entonces a ese elemento.
Puntos clave
- Usa
keypara el significado producido ycodepara la posición física. - Protege los atajos de letras para toda la página frente a contextos de escritura y repeticiones no deseadas.
- Usa
inputpara reaccionar al texto editado, incluido el texto pegado. focusinyfocusoutse propagan por burbujeo;focusyblurno.relatedTargetindica el otro destino u origen del foco.
3.2 Activación y movimiento del puntero
Una persona puede usar un ratón, un dedo, un lápiz o un teclado. Elige un evento según la interacción que necesites. click expresa activación; los eventos de puntero describen la pulsación, el movimiento y la liberación.
Mantén las acciones disponibles mediante el teclado
Un botón nativo emite click cuando se activa con Enter o la barra espaciadora. Un enlace con foco lo hace con Enter. Conectar una acción solo a pointerdown o mousedown deja fuera esas activaciones de teclado. Usa click para acciones como abrir un panel; usa pointerdown cuando el trabajo deba comenzar al pulsar, como al arrastrar.
Un clic con puntero sigue a una pulsación y una liberación. Cuando ocurren en elementos distintos, el clic puede tener como destino su nodo antecesor común más cercano. dblclick sigue a dos clics rápidos; event.detail cuenta los clics rápidos sucesivos. auxclick maneja los clics no principales. contextmenu puede proceder de un clic secundario, una pulsación táctil larga o un comando de menú del teclado.
Usa una sola familia de eventos de puntero
pointerdown, pointermove, pointerup y pointercancel cubren el ratón, la entrada táctil y el lápiz. pointerType identifica el dispositivo como "mouse", "touch" o "pen"; pointerId distingue los punteros activos, incluidos dos dedos.
Las familias más antiguas de eventos de ratón y táctiles siguen existiendo. Los navegadores pueden emitir eventos de ratón por compatibilidad después de los eventos de puntero. Por tanto, registrar tanto pointerdown como mousedown para la misma acción puede ejecutarla dos veces.
Adapta las coordenadas a la tarea
| Coordenadas | Origen |
|---|---|
clientX, clientY | Esquina superior izquierda del área de visualización visible |
pageX, pageY | Esquina superior izquierda del documento, incluido el desplazamiento |
offsetX, offsetY | Borde del relleno del elemento de destino |
Cuando el documento se ha desplazado 300 px hacia abajo y un clic está 100 px por debajo de la parte superior del área de visualización, clientY es 100 y pageY es 400. Mantén ambas posiciones en el mismo sistema de coordenadas al calcular el movimiento. Un cambio de destino anidado también puede cambiar el origen de las coordenadas offset.
button indica qué botón cambió en un evento de pulsación o liberación: 0 principal, 1 central, 2 secundario. buttons indica la combinación que se mantiene pulsada: el principal aporta 1, el secundario 2 y el central 4. Así, buttons === 3 significa que se mantienen pulsados el principal y el secundario; no significa que haya cambiado el botón central.
Elige el comportamiento de paso del puntero para todo un control
Los eventos over/out se propagan por burbujeo y pueden dispararse cuando el puntero cruza los límites de un hijo. Los eventos enter/leave no se propagan por burbujeo y describen la entrada o salida del elemento completo, incluidos sus hijos. Una información emergente puede evitar el parpadeo al cruzar los límites de los hijos con pointerenter y pointerleave.
Los usuarios de pantallas táctiles y de teclado no pasan el puntero. Ofrece la información esencial también al hacer clic o recibir el foco. Para un efecto visual de paso del puntero sin comportamiento adicional, CSS :hover es más sencillo que un detector.
Puntos clave
- Usa
clickpara la activación, incluida la activación nativa mediante teclado. - Los eventos de puntero cubren el ratón, la entrada táctil y el lápiz con una sola familia de eventos.
- Elige coordenadas del mismo sistema para los cálculos.
buttonidentifica un cambio;buttonsindica la combinación que se mantiene pulsada.- Los eventos enter/leave tratan un elemento y sus hijos como un conjunto.
- La información esencial debe ser accesible por otros medios además del paso del puntero.
3.3 Un arrastre que sigue al puntero
El arrastre necesita conservar información entre varios eventos. Guarda las coordenadas iniciales del puntero y la posición inicial del elemento. Cada movimiento aplica entonces la diferencia respecto a ese punto de partida, para que el elemento no salte colocando su esquina debajo del puntero.
Calcula el desplazamiento
Una caja comienza en left: 100px; top: 50px. El puntero comienza en las coordenadas del área de visualización 220, 140 y se mueve a 260, 125. El cambio horizontal es de 40 px; el cambio vertical es de −15 px. Al sumar esos cambios a la posición inicial de la caja se obtiene left: 140px; top: 35px.
Usa coordenadas de cliente coherentes para ambas lecturas del puntero. El cálculo general es la posición inicial del elemento más el desplazamiento del puntero. No sumes cada desplazamiento completo a la posición ya actualizada de la caja, pues volverías a contar el movimiento anterior.
Guarda y captura un puntero
Para una caja sin margen y con posición absoluta dentro de un contenedor con posición relativa:
let start = null;
box.addEventListener("pointerdown", (event) => {
if (start || event.button !== 0) return;
start = { id: event.pointerId, x: event.clientX,
y: event.clientY, left: box.offsetLeft,
top: box.offsetTop };
box.setPointerCapture(event.pointerId);
});
box.addEventListener("pointermove", (event) => {
if (!start || event.pointerId !== start.id) return;
box.style.left = start.left +
(event.clientX - start.x) + "px";
box.style.top = start.top +
(event.clientY - start.y) + "px";
});
function endDrag(event) {
if (start && event.pointerId === start.id) start = null;
}
box.addEventListener("pointerup", endDrag);
box.addEventListener("pointercancel", endDrag);
El ID almacenado descarta los movimientos y la liberación de un segundo puntero. offsetLeft y offsetTop proporcionan las posiciones iniciales como números; los estilos necesitan el sufijo "px".
La captura del puntero mantiene los eventos posteriores de ese puntero dirigidos a la caja incluso cuando este sale de ella. El navegador libera la captura después de soltar o cancelar el puntero. releasePointerCapture(id) puede liberarla antes.
Maneja la intervención del navegador
Un gesto táctil puede convertirse en desplazamiento o zoom de la página y provocar pointercancel. Termina el arrastre tanto ahí como en pointerup; de lo contrario, un estado anterior puede seguir moviendo la caja más tarde. Aplica touch-action: none a la superficie de arrastre antes de la interacción para reservar ese gesto. Limita la regla a esa superficie para que el resto de la página pueda desplazarse.
Usa user-select: none en la superficie arrastrable para evitar seleccionar texto. Establece draggable="false" en sus imágenes para impedir el comportamiento nativo de arrastre de imágenes.
Ofrece otra forma de mover
Proporciona movimiento mediante teclado para un control con foco y botones de movimiento en los que se pueda hacer clic o tocar. Las teclas de dirección por sí solas no proporcionan una alternativa al arrastre con un solo puntero. El criterio de éxito 2.5.7 de WCAG 2.2 exige esa alternativa salvo que el arrastre sea esencial o sea un comportamiento del navegador sin modificar. Los botones nativos de movimiento pueden servir tanto a usuarios de teclado como de puntero.
La API HTML independiente de arrastrar y soltar usa dragstart, dragover y drop para transferir datos, como archivos. Su secuencia de eventos es distinta de este cambio de posición basado en el puntero.
Puntos clave
- Calcula cada posición a partir de la posición inicial almacenada más el desplazamiento del puntero.
- Captura el puntero activo y sigue su ID.
- Termina el arrastre tanto en
pointerupcomo enpointercancel. - Aplica las reglas táctiles y de selección a la superficie de arrastre.
- Proporciona acceso mediante teclado y una alternativa en la que se pueda hacer clic o tocar.
4Maneja formularios y valida los datos de entrada antes del envío
Los formularios combinan varios tipos de estado: texto, valores numéricos, opciones marcadas y nombres repetidos. Lee esos valores en el evento adecuado y después maneja el envío del formulario como una sola acción. Las restricciones HTML proporcionan reglas de validación habituales, mientras que JavaScript añade relaciones entre campos y respuestas más claras. Un formulario útil conserva el trabajo del usuario, explica cada corrección y sitúa el foco donde corresponde la siguiente edición.
4.1 Lee los valores cuando cambian
Los controles de formulario exponen distintos tipos de estado. El value de un campo de texto es una cadena de caracteres; el checked de una casilla de verificación es un booleano. Leer la propiedad adecuada es el primer paso para obtener cálculos y vistas previas fiables.
Convierte los números de forma deliberada
Incluso <input type="number"> tiene un value de tipo cadena. Con el texto "3", sumar 1 da "31". Number(input.value) + 1 da 4. Un campo numérico también ofrece valueAsNumber.
Una entrada vacía necesita una decisión propia. Number("") da 0, lo que puede hacer que una cantidad ausente parezca un cero real. Comprueba primero input.value === "" o usa valueAsNumber, que da NaN para un campo numérico vacío. Comprueba ese resultado con Number.isNaN().
Lee el estado seleccionado
| Control | Propiedad que se debe leer |
|---|---|
| Texto, textarea, lista desplegable | value |
| Campo numérico usado en operaciones aritméticas | valueAsNumber, o convertir un value no vacío |
| Casilla de verificación | checked para el estado marcado o desmarcado |
Grupo de botones de opción llamado size | form.elements.size.value para el valor seleccionado |
El value de una casilla de verificación es su texto configurado para el envío, o "on" si no se proporciona ninguno. No indica si está marcada. Una lista desplegable indica el valor de la opción elegida. Para encontrar el propio elemento de opción seleccionado, consulta input[name="size"]:checked.
Relaciona el evento con la actualización
input informa de las ediciones del usuario mientras ocurren, incluidas la escritura y el pegado. change informa de una elección confirmada: después de que un campo de texto modificado pierda el foco, o inmediatamente cuando cambia una casilla de verificación, un botón de opción o una lista desplegable.
message.addEventListener("input", () => {
counter.textContent = 280 - message.value.length +
" characters left";
});
Después de «Hello», se han usado cinco caracteres y el contador muestra «275 characters left». Un detector de keydown se ejecutaría antes de que el nuevo carácter entrara en el valor y no detectaría el pegado mediante un menú. Asignar message.value desde el código no dispara ni input ni change; llama también directamente a la función de actualización.
Recopila los campos con nombre de un formulario
form.elements.email.value accede a un control por su nombre o ID. Usa form.elements["first-name"] para un nombre con guion. El envío usa nombres: un ID por sí solo no incluye un campo en los datos.
const data = new FormData(form);
const email = data.get("email");
const topics = data.getAll("topics");
FormData recopila los pares de nombre y valor actuales que cumplen los requisitos. Se omiten las casillas sin marcar, los controles deshabilitados y los campos sin nombre. get() devuelve el primer valor de un nombre; getAll() conserva los valores repetidos, como varios temas marcados. Los controles de archivo pueden proporcionar objetos de archivo; los controles de texto normales proporcionan cadenas de caracteres.
Object.fromEntries(data) resulta cómodo para nombres distintos, pero solo conserva el último par de los nombres repetidos. Elige getAll() cuando importe cada selección. Como input se propaga por burbujeo, un detector del formulario puede leer event.target para actualizar el campo que haya cambiado.
Puntos clave
- Convierte la cadena de un campo antes de operar y trata por separado un campo numérico vacío.
- Lee el estado de una casilla de verificación en
checked. - Usa
inputpara ediciones en tiempo real ychangepara elecciones confirmadas. FormDataincluye los controles con nombre que cumplen los requisitos, no todos los controles.getAll()conserva los valores de nombres repetidos; la conversión a objeto no.- Asignar un valor mediante código no emite eventos de edición.
4.2 Maneja el envío del formulario
El envío pertenece al formulario. Escucha su evento submit para manejar por una sola vía un clic en el botón de envío, Enter en un campo de una sola línea y una llamada a requestSubmit() desde un script.
Sustituye la navegación por una actualización de la página
Normalmente, un formulario envía datos a la URL de su action y carga la respuesta. Sin una acción, el destino es la página actual. El método GET predeterminado coloca los datos en la URL. Para actualizar la página actual con JavaScript, cancela esa acción predeterminada:
const form = document.querySelector("#new-todo");
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
const title = data.get("title").trim();
if (title === "") return;
addTodo(title);
form.reset();
});
Este formulario tiene un campo de texto habilitado llamado title. El detector elimina los espacios de los extremos, ignora un resultado vacío, añade un elemento y restablece los campos a sus valores iniciales. reset() no siempre significa dejar vacío; un control con un valor inicial vuelve a ese valor.
Si el elemento aparece brevemente y desaparece al volver a cargar la página, comprueba si falta preventDefault(). Un detector de clic solo en el botón no es el punto adecuado para manejar el formulario, especialmente cuando otra vía lo envía.
Conoce qué desencadena el envío
El envío implícito con Enter puede hacer clic en el botón de envío predeterminado habilitado. Enter en un área de texto inserta un salto de línea. Un botón dentro de un formulario es un botón de envío por defecto cuando se omite su tipo, así que un botón «Mostrar contraseña» debe usar explícitamente type="button".
Cuando hay varios botones de envío, event.submitter identifica el que se usó. Puede ser null en vías sin un botón específico, por lo que el código debe comprobarlo antes de leerlo:
const button = event.submitter;
const intent = button ? button.value : "save";
const data = button ? new FormData(form, button) :
new FormData(form);
Un botón de envío con nombre como name="intent" value="publish" aporta ese par cuando se pasa a FormData. Una llamada a FormData(form) por sí sola no incluye el par de nombre y valor de un botón de envío.
Conserva la vía de validación
| Vía de envío | Validación automática | Detector de submit |
|---|---|---|
Activación de envío o requestSubmit() | Se ejecuta salvo que los atributos de validación la omitan | Se ejecuta si la validación lo permite |
form.submit() | Se omite | Se omite |
La validación integrada se ejecuta antes del evento submit. Si un campo obligatorio vacío no supera la comprobación, el navegador informa del problema y no despacha submit. Una comprobación personalizada situada solo dentro de ese detector no se ejecutará en ese intento fallido. novalidate en el formulario desactiva el paso de validación automática.
Usa requestSubmit() cuando el código necesite la vía normal de validación y del evento submit. Solicita el envío sin despachar un clic en el botón. Pasa un botón de envío concreto a requestSubmit(button) cuando deban aplicarse las opciones de envío de ese botón.
Puntos clave
- Maneja
submiten el formulario para cubrir las vías de envío del usuario y del código. - Cancela el envío predeterminado cuando JavaScript sustituya la navegación.
- Asigna
type="button"a los botones del formulario que no sean de envío. - Usa
submitterpara distinguir los botones de envío e incluir sus datos cuando sea necesario. - La validación precede a
submit;requestSubmit()conserva esa vía. submit()omite la validación y el evento submit.reset()restablece los valores iniciales.
4.3 Deja que HTML describa las reglas de validación
HTML puede describir muchas reglas de entrada sin una comprobación personalizada de JavaScript. El navegador comprueba esas reglas durante el envío y expone los resultados para tu código. Empieza con las restricciones que correspondan a los datos que necesitas.
Elige una restricción
| Atributo | Regla |
|---|---|
required | Se debe proporcionar un valor o una selección obligatoria. |
type="email" o "url" | El valor debe tener la sintaxis esperada. |
min, max, step | Los valores numéricos o de fecha deben ajustarse a los límites e incrementos. |
minlength, maxlength | El texto introducido por el usuario debe cumplir una regla de longitud. |
pattern | Un valor de texto no vacío debe coincidir con una expresión que abarque todo el valor. |
Para pattern="[0-9]{5}", «12345» pasa la comprobación; «1234», «123456» y «12345a» no la pasan. El patrón abarca todo el valor, no cualquier subcadena coincidente. Una entrada vacía omite la comprobación del patrón, así que añade required cuando no se permita dejarla vacía.
La validación de correo electrónico comprueba la sintaxis, no si existe un buzón. Tanto «ann@example.com» como «ann@example» cumplen la regla de correo electrónico del navegador, mientras que «ann.example.com» no la cumple. Un patrón adicional puede imponer un formato más específico cuando la aplicación lo requiera.
Lee el motivo del fallo
El objeto validity de un campo contiene indicadores booleanos. valueMissing, typeMismatch, patternMismatch, tooShort y tooLong describen texto ausente o con formato incorrecto. rangeUnderflow, rangeOverflow y stepMismatch describen restricciones numéricas. badInput cubre una entrada que el navegador no puede convertir en un valor utilizable, y customError registra un mensaje de validez personalizada. valid es verdadero cuando ninguna restricción falla.
Para un campo numérico con min="0" max="10" step="2", 12 supera el máximo, 7 no coincide con el incremento permitido y 6 pasa la comprobación. Más de un indicador puede ser verdadero; no supongas que cada campo no válido tiene exactamente un problema.
Comprueba sin mostrar o informa de forma visible
checkValidity() devuelve un booleano y dispara invalid en los controles que no pasan la comprobación sin mostrar el mensaje del navegador. reportValidity() comprueba y también informa del problema al usuario. Llama a cualquiera de los dos en un campo o un formulario. validationMessage proporciona el mensaje en el idioma del navegador. El evento invalid no se propaga por burbujeo.
Las restricciones de longitud del texto se aplican al texto introducido por el usuario. Asignar input.value = "ab" en el código no hace que tooShort sea verdadero solo porque minlength sea 3. maxlength normalmente también impide que el usuario siga escribiendo más allá del límite.
Muestra los errores en un momento útil
:invalid se aplica inmediatamente, por lo que un campo obligatorio sin tocar ya coincide. :user-invalid espera a una interacción del usuario o a un intento de envío y puede evitar que el campo aparezca en rojo demasiado pronto. Usa también texto para explicar el error, además de un estilo visual.
novalidate omite la validación automática del envío para el formulario; formnovalidate la omite para un botón de envío. No eliminan las restricciones ni desactivan checkValidity(). Las comprobaciones del navegador ayudan a los usuarios a corregir errores; el servidor debe volver a validar los datos recibidos porque una solicitud puede evitar la página.
Puntos clave
- Usa restricciones HTML para valores obligatorios, sintaxis, rangos, incrementos, longitud y patrones.
- Un patrón coincide con todo el valor no vacío; usa
requiredpara rechazar una entrada vacía. - Lee los indicadores de
validitypara identificar por qué un campo no pasó la comprobación. checkValidity()comprueba;reportValidity()también muestra información al usuario.- Los atributos que omiten la validación permiten seguir usando la API de validez.
- Valida también los datos recibidos en el servidor.
4.4 Reglas personalizadas y mensajes de error útiles
Una regla que implica dos campos necesita código. Por ejemplo, una confirmación debe coincidir con una contraseña, o una fecha final debe ser posterior a una fecha inicial. Mantén la regla separada de cómo y cuándo se muestra su error.
Establece y borra un error personalizado
Una cadena no vacía pasada a setCustomValidity() marca el campo como no válido y establece customError. Pasar "" borra ese error personalizado; las restricciones de obligatoriedad, patrón u otras pueden seguir sin cumplirse.
function checkMatch() {
const matches = confirm.value === password.value;
confirm.setCustomValidity(matches ? "" :
"The passwords do not match.");
}
password.addEventListener("input", checkMatch);
confirm.addEventListener("input", checkMatch);
Vuelve a comprobar ambas dependencias. Cambiar la primera contraseña puede hacer que una confirmación antes correcta deje de coincidir. Un mensaje que nunca se borra mantiene el campo como no válido después de que la persona corrija la diferencia. Ejecuta también la comprobación inicial cuando los campos puedan comenzar con valores.
Los campos de fecha proporcionan cadenas ISO como "2026-03-10", independientemente del formato de fecha local que se muestre. Los valores no vacíos de estos controles se pueden comparar como texto. Si la regla indica que el final debe ser posterior al inicio, rechaza end.value <= start.value, incluida la igualdad. Trata las fechas ausentes mediante required o una comprobación independiente de valor vacío.
Explica la siguiente acción útil
Un mensaje debe identificar el problema y su corrección. Para un correo electrónico ausente, di «Introduce tu dirección de correo electrónico». Para un texto de correo con formato incorrecto, di «Introduce una dirección de correo electrónico con el formato nombre@example.com». Conserva el valor introducido para que la persona pueda editarlo.
Un campo de nombre obligatorio acepta espacios porque forman una cadena no vacía. Comprueba name.value.trim() === "" cuando los espacios en blanco de los extremos no deban contar. Borra este error personalizado cuando aparezca texto significativo.
Conecta los mensajes con los controles
<label for="email">Email</label>
<input id="email" name="email" type="email"
required aria-describedby="email-error">
<p id="email-error"></p>
Coloca el error en ese párrafo y establece aria-invalid="true" mientras el campo tenga un error. Borra el texto y elimina aria-invalid o establécelo en "false" después de la corrección. aria-describedby conecta la explicación con el campo para la tecnología de asistencia. Usa texto; un borde rojo por sí solo no puede indicar a todo el mundo qué ha fallado.
Elige el flujo de validación
Para mensajes completamente personalizados, añade novalidate y usa un detector de submit en el formulario. Actualiza las reglas personalizadas, examina la validez de los campos, escribe sus mensajes e impide el envío cuando alguno no sea válido. Mueve el foco al primer campo no válido o a un resumen de errores con enlaces. Si JavaScript también maneja localmente el envío correcto, cancela asimismo ese envío predeterminado.
Evita informar de un texto de correo electrónico incompleto en cada pulsación inicial. Una secuencia útil consiste en mostrar los errores después de perder el foco o de enviar el formulario y, después, volver a comprobar un error ya mostrado en input.
Puntos clave
- Los mensajes personalizados no vacíos hacen que un campo sea no válido;
""borra solo ese error personalizado. - Vuelve a comprobar cada campo del que dependa una regla entre campos.
- Para un orden estricto de fechas, rechaza tanto la igualdad como una fecha final anterior.
- Explica una corrección con texto y asóciala al campo.
- Borra los errores visibles y el estado de error ARIA después de la corrección.
- Los mensajes personalizados al enviar necesitan un flujo explícito de validez y foco.
5Construye controles personalizados que todos puedan usar
Elegir un elemento nativo proporciona gran parte del comportamiento de un control antes de escribir ningún detector. Los controles personalizados necesitan el mismo cuidado: un nombre claro, acceso mediante teclado, foco visible y un estado que coincida con lo que comunica la tecnología de asistencia. Los grupos necesitan un movimiento predecible entre elementos. Cuando el contenido se abre, se cierra o desaparece, un destino del foco elegido de forma deliberada permite que el usuario continúe la interacción sin volver a buscar en la página.
5.1 Empieza con un control nativo
Un control necesita algo más que una respuesta al clic. Las personas deben poder encontrarlo, llegar a él, comprender su propósito, accionarlo y ver dónde está el foco. HTML nativo ya proporciona gran parte de este comportamiento.
Elige el elemento adecuado para la tarea
Usa un <button> para una acción, un <a href> para la navegación y un control de formulario con etiqueta para la entrada de datos. <details> con <summary> proporciona un control de despliegue de contenido. Aplica estilos CSS a estos elementos para que encajen en la página. Un botón con foco se activa mediante Enter o la barra espaciadora sin un detector de teclado adicional.
Un div con un detector de clic no tiene ninguna de las características semánticas ni del comportamiento de teclado integrados del botón. Asignarle role="button" cambia lo que anuncia la tecnología de asistencia, pero no permite que reciba el foco ni convierte las pulsaciones de teclas en clics.
| Necesidad | Botón nativo | Div personalizado que actúa como botón |
|---|---|---|
| Rol | Integrado | Añade role="button". |
| Foco mediante teclado | Integrado | Añade tabindex="0". |
| Enter y barra espaciadora | Integrados | Añade manejo del teclado. |
| Nombre accesible | Normalmente su texto | Proporciona texto significativo o un nombre ARIA. |
| Comportamiento deshabilitado | disabled | Comprueba aria-disabled en el código. |
Mantén un orden de foco predecible
tabindex="0" añade un elemento a la secuencia normal de tabulación en el orden del documento. tabindex="-1" permite dar el foco mediante código sin añadir una parada de tabulación. Los valores positivos mueven elementos por delante de la secuencia habitual y son difíciles de mantener; evítalos.
Ninguna de las dos configuraciones añade comportamiento de activación. Un botón personalizado que puede recibir el foco sigue necesitando un detector para manejarlo con el teclado. Un rol, una parada de tabulación y una función de devolución de llamada resuelven partes distintas de la interacción.
Da un nombre a cada control
Un lector de pantalla usa un nombre accesible para distinguir los controles. Un botón de texto puede usar su texto visible. Un campo de texto o una lista desplegable necesita una etiqueta asociada, como <label for="email">Email</label> junto con id="email". aria-labelledby puede hacer referencia a texto visible en otro lugar; aria-label puede proporcionar un nombre cuando no hay texto visible adecuado.
Un botón de cierre que solo contiene un icono puede ser <button type="button" aria-label="Close">×</button>. El nombre describe la acción en lugar de hacer que el usuario deduzca qué significa el símbolo.
Muestra y aplica el estado
Mantén un indicador de foco visible. Si sustituyes el contorno predeterminado, proporciona una alternativa clara con :focus-visible, por ejemplo outline: 3px solid #1a73e8. Eliminarlo sin sustituirlo oculta la posición del usuario de teclado.
Un botón nativo deshabilitado sale de la secuencia de tabulación y no puede activarse normalmente. aria-disabled="true" solo comunica que no está disponible. La acción de clic y teclado de un control personalizado debe comprobar ese estado y terminar sin actuar. Centralizar esa comprobación en la función de activación mantiene la coherencia entre ambas vías de entrada.
Puntos clave
- Usa un elemento nativo que corresponda a la acción o a la entrada de datos.
- Un rol ARIA proporciona significado semántico, no comportamiento.
- Usa
tabindex="0"para el acceso normal con Tab y-1para dar el foco solo desde el código. - Proporciona un nombre accesible y un indicador de foco visible.
- Haz que el código de activación respete
aria-disabled.
5.2 Mantén unidos el comportamiento de teclado y el estado
Un control personalizado debe dar el mismo resultado independientemente de cómo se active. Mantén la acción en una sola función, dirige a ella el uso del teclado y del puntero, y actualiza el estado que leen tanto los estilos como la tecnología de asistencia.
Da activación mediante teclado a un botón personalizado
fakeButton.addEventListener("click", activate);
fakeButton.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
activate();
}
});
Esto es para un botón personalizado que puede recibir el foco. Cancelar la barra espaciadora impide que la página se desplace durante la acción. Un botón nativo ya produce clics al activarse con el teclado, por lo que añadirle una segunda vía de teclado puede ejecutar la acción dos veces.
Elige el estado que corresponda al control
| Control | Estado |
|---|---|
| Botón de alternancia con una etiqueta estable | aria-pressed="true" o "false" |
| Botón que abre un control de despliegue | aria-expanded="true" o "false" |
| Casilla de verificación o interruptor personalizado | aria-checked="true" o "false" |
Un botón de alternancia «Silenciar» conserva esa etiqueta y cambia aria-pressed. Si el diseño cambia la etiqueta de la acción a «Activar sonido», omite aria-pressed. Una casilla de verificación usa role="checkbox" y la barra espaciadora para alternar su estado; un interruptor usa role="switch" para el estado de activado o desactivado.
Los atributos contienen cadenas de caracteres. Compara getAttribute("aria-expanded") con "true", no con el booleano true:
detailsButton.addEventListener("click", () => {
const open = detailsButton.getAttribute(
"aria-expanded") === "true";
detailsButton.setAttribute("aria-expanded", String(!open));
panel.hidden = open;
});
Cuando open era verdadero, el nuevo estado es falso y el panel queda oculto. aria-controls puede identificar el panel por su ID. Los elementos nativos <details> y <summary> proporcionan el comportamiento de despliegue sin este script.
Usa el atributo de estado como fuente para los estilos, por ejemplo [aria-pressed="true"], para que los estados visible y anunciado cambien juntos. Cierra los elementos emergentes con Escape y devuelve el foco a su control de apertura.
Da al grupo una sola parada de tabulación
Las pestañas, las barras de herramientas y los grupos de botones de opción usan Tab para entrar o salir del grupo y las teclas de dirección para moverse dentro de él. El índice de tabulación móvil mantiene el elemento actual en 0 y los demás en -1. Al moverse, establece el elemento anterior en -1, el nuevo en 0 y da el foco al nuevo elemento.
Para tres elementos, moverse a la derecha desde el índice 2 vuelve al 0 con (2 + 1) % 3. Moverse a la izquierda desde 0 usa (0 - 1 + 3) % 3 para llegar a 2. Añade la longitud antes de la operación de resto porque en JavaScript -1 % 3 es −1.
Comprueba con el teclado: entra en el control, ve el foco, actívalo con las teclas adecuadas, muévete dentro de un grupo y cierra un elemento emergente con Escape. Comprueba el estado visible y el estado ARIA en cada cambio.
Puntos clave
- Dirige el clic, Enter y la barra espaciadora de un botón personalizado a una sola acción.
- Los botones nativos ya proporcionan la activación por clic mediante teclado.
- Elige
aria-pressed,aria-expandedoaria-checkedsegún el estado correspondiente. - Compara los valores ARIA como cadenas y actualiza los estilos a partir del mismo estado.
- El índice de tabulación móvil da al grupo una parada de tabulación y movimiento con las teclas de dirección.
- Escape cierra un elemento emergente y devuelve el foco a su control de apertura.
5.3 Mueve el foco cuando cambie la página
El foco determina adónde va la siguiente acción del teclado. Si el control que tiene el foco desaparece, el usuario puede perder su posición. Trata el foco como parte de la actualización de la página, especialmente al abrir un cuadro de diálogo, eliminar un elemento o informar de un error.
Abre un cuadro de diálogo y vuelve a su control de apertura
Recuerda el elemento que tiene el foco antes de abrir. Mueve el foco a la nueva interacción y después restablécelo al cerrar:
let opener;
openButton.addEventListener("click", () => {
opener = document.activeElement;
dialog.showModal();
});
dialog.addEventListener("close", () => {
if (opener && opener.isConnected) opener.focus();
});
showModal() en un cuadro de diálogo nativo mueve el foco a su interior y hace que el resto de la página sea inerte, por lo que no se puede hacer clic en ella ni darle el foco mientras el cuadro modal está abierto. Escape normalmente provoca un evento cancel seguido del cierre y un evento close. Un detector de cancel puede impedir el cierre si la aplicación necesita ese comportamiento.
Elige una ubicación inicial útil para el foco en el cuadro de diálogo, como su primer control pertinente. Para un elemento emergente personalizado, el código debe mover el foco de forma deliberada. Cuando el control de apertura original ya no exista, elige un control útil cercano en lugar de intentar dar el foco a un elemento desconectado.
Permite que un destino útil reciba el foco
Un encabezado o un resumen de errores normalmente no puede recibir el foco. Asígnale tabindex="-1" antes de llamar a .focus(). Podrá recibir el foco de forma deliberada sin convertirse en una parada adicional durante la navegación normal con Tab. Usa 0 solo cuando pertenezca a esa secuencia normal.
Conserva la posición después de eliminar
Antes de eliminar un elemento de lista cuyo botón de eliminación tiene el foco, encuentra el siguiente destino útil. Da el foco al botón del siguiente elemento; si no hay un siguiente elemento, usa el botón del anterior. Si la lista queda vacía, da el foco a su encabezado con tabindex="-1".
Encontrar el destino antes de eliminar evita perder la relación con los elementos vecinos. No envíes el foco a la parte superior del documento después de cada eliminación; la persona debe poder seguir trabajando cerca del cambio.
Mantén el foco en una interacción que continúa
Un elemento emergente puede cerrarse cuando el foco sale de toda su área. El evento focusout, que se propaga por burbujeo, proporciona el destino:
menu.addEventListener("focusout", (event) => {
if (!menu.contains(event.relatedTarget)) {
menu.hidden = true;
}
});
Moverse entre dos controles del menú no lo cierra porque el destino permanece dentro. Un destino null también está fuera; contains(null) es falso. No devuelvas automáticamente el foco al control de apertura cuando el usuario se haya movido de forma intencionada a otro control de la página.
Haz que los cambios sean predecibles
Después de una validación personalizada fallida de un formulario, da el foco al primer campo no válido o a un resumen de errores que enlace a ellos. La validación integrada ya da el foco a un campo que no supera la comprobación. Mantén disponible el texto del error para que la persona sepa qué corregir.
No saltes a un campo nuevo mientras el usuario escribe ni navegues en cuanto cambie una lista desplegable. Deja que una acción clara confirme el cambio. El movimiento del foco debe ayudar a realizar una acción deliberada o a recuperarse de un error, sin interrumpir la entrada normal.
Puntos clave
- Recuerda el control de apertura y restablece el foco en un destino disponible cuando se cierre un cuadro de diálogo.
- Usa
tabindex="-1"para un encabezado o resumen que reciba el foco desde el código. - Después de eliminar, da el foco a un elemento vecino o al encabezado de la lista vacía.
- Usa
relatedTargetpara distinguir los movimientos de foco internos de la salida de un elemento emergente. - Da el foco a un destino de error después de una validación fallida; evita los saltos de foco inesperados.
Ejemplos de Detectores de eventos
1Construye e inspecciona detectores de eventos
13 pasos
- 1IntroducciónHaz que un botón responda
Un detector conecta un evento con una función. Construye un pequeño contador eligiendo el botón, registrando su acción y conservando el total entre llamadas. Después inspecciona un
clickanidado y controla la duración del detector.Observa la diferencia entre el código de configuración, que se ejecuta ahora, y la función de devolución de llamada, a la que el navegador llama cuando ocurre el evento elegido.
- 2EjemploAlmacena el total fuera de la función de devolución de llamada
Selecciona el botón existente y almacena su recuento antes del registro:
const button = document.querySelector("#like"); let likes = 0; button.addEventListener("click", () => { likes = likes + 1; button.textContent = "Likes: " + likes; });Cada llamada suma al total anterior. Después de tres clics, la etiqueta es «Likes: 3». Un recuento declarado dentro de la función de devolución de llamada comenzaría de nuevo en cada llamada.
- 3ConsejoPasa la acción sin llamarla
addEventListener("click", showMenu)pasa una función para usarla más tarde.showMenu()la llamaría durante la configuración. Cuando se necesiten argumentos, usa() => showMenu("main").Carga el script después de que exista el botón, por ejemplo con
defer, y conserva la escritura exacta del evento. Un detector de"Click"no recibe los eventos"click"normales. - 4DesafíoCorrige un registro
Un botón debe abrir el panel de consejos en cada activación. Actualmente, la configuración usa
button.addEventListener("click", openTips()). ¿Qué debe cambiar y por qué?---reveal---
Pasa
openTipssin paréntesis. Así proporcionas la función de devolución de llamada que se ejecutará para unclickposterior. Llamar aopenTips()durante la configuración abre el panel inmediatamente y pasa su resultado en lugar de conservar la acción para el evento. - 5ExplicaciónLee las dos propiedades de elementos
Para un botón que contiene una imagen, hacer clic en la imagen la convierte en el
target. En el detector del botón,currentTargetes el botón.targetindica dónde comenzó el evento; eltargetactual indica dónde se registró esta función de devolución de llamada. Esa diferencia explica por qué leer eldatasetde un botón desdetargetpuede fallar cuando elclickcae en su imagen. - 6EjemploLee los datos del botón que los contiene
Supón que
buttontienedata-id="7"y contiene un icono:button.addEventListener("click", (event) => { console.log(event.currentTarget.dataset.id); });El resultado es
"7"tanto para unclicken el icono como para unclicken el fondo del botón. La misma expresión funciona en un detector con una función flecha o normal. Elthisde una función flecha no identifica automáticamente el botón. - 7DesafíoIdentifica el elemento del detector
Una sección contiene un botón con un span. Se registra un detector de
clicksolo en la sección y se hace clic en el span. ¿Qué identificantargetycurrentTargetdurante esa función de devolución de llamada?---reveal---
targetidentifica el span donde comenzó el evento.currentTargetidentifica la sección donde se registró el detector. El origen del evento no se sustituye por el nodo antecesor que lo recibe. - 8ExplicaciónConserva la referencia para eliminar
La eliminación necesita el mismo tipo de evento, objeto de función de devolución de llamada y configuración de captura:
const handler = () => closePanel(); button.addEventListener("click", handler); button.removeEventListener("click", handler);Una función anónima se puede eliminar si se guarda de esta forma. Escribir una nueva
() => closePanel()en la llamada de eliminación crea otro objeto y no coincide con el registro anterior. - 9ConexiónTermina varios registros a la vez
Una función temporal puede pasar una señal de
AbortControllera varios registros. Llamar acontroller.abort()elimina esos detectores, incluidas las funciones de devolución de llamada guardadas o anónimas.Usa un controlador nuevo para otro periodo de actividad. Para una sola respuesta de ejecución única,
{ once: true }elimina ese registro después de que se ejecute. Ninguna de las dos opciones impide que se acumulen funciones de devolución de llamada distintas antes de que termine su periodo de actividad. - 10DesafíoDiagnostica una configuración repetida
Al abrir un panel se registra cada vez un nuevo detector anónimo de teclado en el documento. Al cerrarlo se oculta el panel, pero el detector permanece. Después de tres aperturas, una tecla desencadena tres acciones. ¿Cómo se puede corregir el ciclo de vida?
---reveal---
Elimina los registros cuando se cierre el panel, por ejemplo cancelando su señal compartida, y crea un controlador nuevo al volver a abrirlo. Como alternativa, guarda una función de devolución de llamada y elimina esa misma referencia. Ocultar el panel no elimina un detector del documento.
- 11Idea claveDespacha una notificación inmediatamente
Para avisar a otro código, crea un
CustomEventcon sus datos endetail. Establecebubbles: truesi los nodos antecesores deben recibirlo:item.dispatchEvent(new CustomEvent("cart:add", { detail: { id: 42 }, bubbles: true }));Los detectores se ejecutan antes de que termine la llamada. El evento despachado tiene
isTrusteden falso. Es una notificación que envía tu código, no una solicitud para que el navegador espere una acción del usuario. - 12DesafíoCombina origen y duración
El manejador de
clickde un botón usa su propiodata-keyy debe actuar solo en la primera activación. Contiene un icono SVG. ¿Qué propiedad y opción mantienen correctos ambos requisitos?---reveal---
Lee
event.currentTarget.dataset.keyen el detector directo del botón y regístralo con{ once: true }. La propiedad identifica el botón que contiene los datos incluso para unclicken el icono. La opción elimina ese registro después de su primera llamada. - 13RepasoComprueba un detector en tres puntos
Cuando una interacción falle, examina la configuración, los datos de la función de devolución de llamada y su duración. Confirma que el elemento existe y que el nombre del evento es exacto; examina
targetycurrentTargetdurante la llamada; después busca eliminaciones que falten o registros repetidos. Estas comprobaciones distinguen un detector que nunca recibe un evento de uno que lo recibe correctamente pero actúa sobre el elemento equivocado.
2Sigue eventos y delega acciones
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue un clic por una tarjeta
- 2EjemploLee el recorrido ascendente
- 3ExplicaciónAñade el recorrido descendente
- 4DesafíoSigue un árbol más pequeño
- 5ExplicaciónSustituye la navegación
- 6Idea claveDetener el recorrido es una decisión distinta
- 7DesafíoMantén activo un observador padre
- 8ExplicaciónMueve el detector a la lista
- 9EjemploEncuentra y comprueba el botón de eliminación
- 10DesafíoManeja un icono anidado
- 11ConsejoUsa un evento que llegue al contenedor
- 12DesafíoMantén separadas la apertura y el cierre
- 13RepasoElige un punto de actuación para cada acción
3Maneja teclas y construye un arrastre con puntero
16 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónElige el nivel de entrada adecuado
- 2ExplicaciónDistingue el significado de la posición
- 3ConsejoProtege la escritura y maneja las repeticiones
- 4DesafíoElige un evento para una vista previa
- 5ConexiónPermite que la activación incluya el teclado
- 6EjemploCambia de sistema de coordenadas
- 7DesafíoExamina un clic con la página desplazada
- 8Idea claveEvita que el paso del puntero sea la única vía
- 9EjemploCalcula un movimiento de arrastre
- 10ExplicaciónGuarda el inicio y captura su puntero
- 11EjemploAplica el desplazamiento en cada movimiento
- 12ConsejoTermina incluso si interviene el navegador
- 13DesafíoPredice un arrastre que continúa
- 14ConexiónOfrece movimiento sin arrastrar
- 15DesafíoDiagnostica dos fallos de arrastre
- 16RepasoRelaciona el evento con la tarea
4Construye una interacción completa de formulario
16 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue un formulario desde la edición hasta el envío
- 2EjemploLee un número sin concatenar
- 3ExplicaciónLee opciones y nombres repetidos
- 4DesafíoConserva cada tema seleccionado
- 5ExplicaciónManeja el envío en el formulario
- 6EjemploLee, añade y restablece
- 7DesafíoConserva la vía normal de envío
- 8EjemploLee los fallos independientes de restricciones
- 9Idea claveUn patrón no exige un valor
- 10DesafíoComprueba un rango con incrementos
- 11ExplicaciónAñade una regla que conecte dos campos
- 12ConsejoDistingue la validez del momento de mostrar el error
- 13DesafíoCorrige una regla de fechas desactualizada
- 14ConexiónAsume la responsabilidad al omitir los mensajes del navegador
- 15DesafíoCompleta el flujo de recuperación
- 16RepasoComprueba las vías válidas y no válidas
5Construye controles y conserva la posición del usuario de teclado
14 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz que una interacción funcione con el teclado
- 2ExplicaciónUsa el comportamiento que HTML ya proporciona
- 3ConsejoPermite recibir el foco por la vía prevista
- 4DesafíoCorrige una acción inaccesible
- 5EjemploMantén una sola función de activación
- 6ExplicaciónExpresa el tipo de estado adecuado
- 7DesafíoInvierte correctamente un control de despliegue
- 8EjemploMueve una parada de tabulación dentro de un grupo
- 9ConexiónRecuerda desde dónde se abrió un cuadro de diálogo
- 10EjemploMantén dentro un movimiento de foco interno
- 11DesafíoElige el foco después de eliminar
- 12DesafíoSigue una actualización de grupo
- 13DesafíoMantén predecible un elemento emergente
- 14RepasoPrueba la secuencia completa del control
Ejercicios de Detectores de eventos
Set 1: Sigue acciones anidadas y delegación10 Preguntas de Práctica
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Pregunta 3 de 10
focus, pero dar el foco a su campo hijo no lo llama. ¿Cómo puede el formulario observar el foco de sus descendientes sin usar captura?Mostrar respuestaOcultar respuesta
focusin se propaga por burbujeo desde el campo hasta su formulario, por lo que un detector normal del formulario puede observarlo. focus y blur no se propagan por burbujeo. Una opción del detector no puede cambiar si el evento del navegador se propaga; leer una propiedad dentro de una función que nunca se ejecuta no resuelve la ausencia del evento.Pregunta 4 de 10
event.stopPropagation(). ¿Qué ocurre con su navegación normal?Mostrar respuestaOcultar respuesta
preventDefault() en un detector que pueda cancelar el evento cuando el código sustituya la navegación por una acción en la página.Pregunta 5 de 10
preventDefault(). También hay un detector normal de submit en una sección que lo rodea. ¿Qué puede observar el detector de la sección?Mostrar respuestaOcultar respuesta
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
closest() comienza en el SVG pulsado y sube hasta el botón. matches() solo comprueba el SVG, por lo que no puede identificar el control que lo rodea. Buscar hacia abajo con querySelector() también sigue la dirección equivocada para un botón que contiene el destino del evento.Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Pregunta 9 de 10
closest(".action") dentro de un contenedor anidado en un elemento exterior .action. Un clic en un espacio vacío encuentra ese elemento exterior. ¿Qué comprobación limita el manejo al contenedor?Mostrar respuestaOcultar respuesta
closest() puede subir más allá del contenedor del detector y devolver un nodo antecesor exterior. Después de comprobar si no hay coincidencia, usa container.contains(match) para rechazar ese resultado. Comprobar currentTarget === container rechazaría todos los eventos de este detector, incluidos los clics válidos en sus propios controles hijos.Pregunta 10 de 10
closest(".remove") y comprobaciones de pertenencia. Los botones nuevos funcionan, salvo en una fila cuyo detector del icono llama a stopPropagation(). El relleno del botón de esa misma fila sí funciona. ¿Por qué ocurre esta diferencia?Mostrar respuestaOcultar respuesta
closest() puede manejar un icono como destino, pero no puede ayudar a una función delegada que nunca recibe el evento.Set 2: Configura e inspecciona detectores de eventos10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
showHints solo cuando se active. ¿Qué registro consigue eso?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
addEventListener. ¿Qué cambio de carga permite esperar a que se analice el HTML antes de seleccionar?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
const items = document.querySelectorAll(".choice"); items.addEventListener("click", choose);. Todas las opciones ya existen. ¿Por qué falla y cómo debe cambiar la configuración?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
button.onclick = first; button.onclick = second;. ¿Qué ocurre al hacer clic una vez en el botón?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
target y currentTarget durante ese detector?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
<button data-code="r8"><span>Run</span></button> lee event.target.dataset.code. ¿Por qué puede devolver undefined y qué expresión de un detector directo lee el código del botón?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
currentTarget: null. ¿Cómo puede quien desarrolla inspeccionar el elemento del detector durante la llamada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
{ capture: true }. Se pasan el mismo tipo y función a removeEventListener, pero no se proporciona un tercer argumento. ¿Por qué permanece la función?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
board.addEventListener("click", () => mark()). Al iniciar dos veces, un clic en el tablero coloca dos marcas. ¿Qué cambio de configuración evita esa acumulación?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Un botón que no tiene otros detectores recibe estos registros y después se pulsa dos veces. ¿Cuántas veces se ejecuta log en total?
button.addEventListener("click", log, { once: true });
button.addEventListener("click", log);
button.addEventListener("click", log, true);
button.removeEventListener("click", log);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Maneja la entrada de teclado, puntero y arrastre10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
keydown. ¿Qué cambio corrige ambos síntomas?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
pointerdown. ¿Qué evento debe manejar la acción?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
clientY 75. ¿Cuál es su coordenada vertical relativa al documento?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
pointerout. ¿Qué evento detecta mejor la salida de toda la tarjeta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
left: 65px; top: 90px. Su puntero comienza en 180, 210 y se mueve a 155, 245 en coordenadas de cliente. ¿A qué posición debe llegar la caja?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Lee datos de formulario y diagnostica la validación10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
"6". ¿Qué produce input.value + 2?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
topic con los valores "art" y "code" en el orden del documento, un topic sin marcar con el valor "music" y una nota deshabilitada con nombre. ¿Qué devuelve new FormData(form).getAll("topic") y por qué es arriesgada la conversión a objeto?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
form.submit() y el detector del formulario que impide la navegación nunca se ejecuta. ¿Qué llamada restablece el flujo esperado y qué ocurre antes de que se corrija ese campo?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
pattern="[A-Z]{3}" y es opcional. ¿Un valor vacío supera esta restricción de patrón?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
min="2" max="14" step="4". El usuario introduce 12. ¿Qué falla?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
novalidate y comprueba los campos en el orden del documento al enviarse. Su campo de cantidad es obligatorio y tiene min="2" max="14" step="4"; ahora contiene 10 y se borró un mensaje personalizado anterior con setCustomValidity(""). El siguiente campo es un correo electrónico obligatorio que está vacío. ¿Qué debe bloquear este intento y recibir el foco?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
end.value < start.value y solo se ejecuta al editar la fecha final. ¿Qué dos cambios corrigen los fallos restantes entre campos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 5: Construye acceso mediante teclado y conserva el foco10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
role="button" y un detector de clic. ¿Qué proporciona el propio rol?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
activate(). Tiene aria-disabled="true". La función comienza con if (button.getAttribute("aria-disabled") === true) return;, pero ambas vías siguen actuando. ¿Qué corrección aplica el estado deshabilitado y permite actuar cuando el estado es la cadena «false»?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
aria-expanded="true" y un panel visible. Su manejador de clic lee el estado expandido anterior. ¿Cuál debe ser el siguiente estado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
tabindex="-1". El botón de eliminación con foco del último elemento lo elimina; después también se elimina el elemento restante. ¿Qué secuencia de foco permite continuar usando el teclado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Acción predeterminada
Activación
click para el uso con puntero y con teclado nativo.click tanto si se pulsa con la barra espaciadora como con un ratón.Alternativa con un solo puntero
Atributo aria-describedby
aria-describedby asocia un control con texto descriptivo enumerando los ID de los elementos que contienen ese texto.email-error para que la tecnología de asistencia pueda conectar el error con él.Atributo aria-invalid
aria-invalid comunica que el valor introducido en un control tiene un error; el código debe actualizarlo cuando cambie el error.aria-invalid="true" mientras se muestra junto a él su mensaje de corrección.Atributo de datos
data- y almacena información de la aplicación disponible mediante el dataset de un elemento.data-action="clear" como button.dataset.action para seleccionar una acción conocida.Atributo novalidate
novalidate omite la validación automática de restricciones durante el envío y deja disponibles las restricciones y los métodos de validez.novalidate para que su detector de submit pueda examinar cada campo por sí mismo.Atributo pattern
pattern define una restricción de expresión regular para todo el valor de los campos de texto compatibles, que se aplica solo cuando su valor no está vacío.required para rechazar un campo vacío.Atributo tabindex
tabindex controla la participación en la tabulación normal y la capacidad de recibir el foco desde el código: 0 sigue el orden normal y −1 queda fuera de esa secuencia.tabindex="-1" para que el código pueda darle el foco sin añadir una parada normal de tabulación.B
Botón de alternancia
aria-pressed para indicar el estado actual.aria-pressed cambia entre verdadero y falso.Botón de envío
event.submitter cuando un botón concreto inició esa vía.C
Captura del puntero
Comprobación de clic fuera del elemento
Control de apertura
Control de despliegue de contenido
aria-expanded.Control nativo
Controlador de cancelación (AbortController)
abort().Coordenadas del área de visualización
clientX y clientY.Coordenadas del documento
pageX y pageY, incluido el desplazamiento.pageY 400.Cuadro de diálogo modal
showModal() abre el cuadro de diálogo nativo y mueve el foco a su contenido.D
Datos de formulario (FormData)
data.getAll("topics") obtiene juntas todas las seleccionadas.Delegación de eventos
Despacho síncrono
dispatchEvent(), antes de que ese método devuelva el control al código que lo llamó.dispatchEvent() aparece después de los mensajes de sus detectores.Detector de eventos
Detector de eventos pasivo
preventDefault() en un detector táctil pasivo no detiene el desplazamiento del navegador.E
Eliminación de espacios en blanco iniciales y finales
name.value.trim() === "" detecta un nombre compuesto solo por espacios.Estado ARIA
aria-checked del interruptor se actualiza cuando cambia su aspecto visible de activado o desactivado.Estado de arrastre
Estado de validez (ValidityState)
validity de un control, con indicadores booleanos que identifican las restricciones incumplidas.stepMismatch aunque esté entre su mínimo y su máximo.Evento
click permite que la página responda cuando una persona activa el botón para avanzar en la galería.Evento change
change informa de un cambio confirmado por el usuario, como salir de un campo de texto editado o elegir un nuevo estado de una casilla de verificación o lista desplegable.change cuando el usuario lo deja para pasar al siguiente control.Evento de puntero
pointermove puede leer el movimiento de un ratón o de un contacto táctil.Evento input
input informa de una edición del usuario después de que cambie el valor de un control, incluidas las ediciones de texto realizadas sin pulsar una tecla física.input del campo.Evento keydown
keydown informa de una pulsación de tecla antes de su efecto normal de edición y puede repetirse mientras la tecla física permanece pulsada.keydown antes de cerrar el panel abierto.Evento personalizado (CustomEvent)
detail y configurarse para propagarse por burbujeo hasta los nodos antecesores."cart:add" con su identificador de elemento en detail del evento.Evento pointercancel
pointercancel informa de que el navegador ha cancelado la interacción del puntero, por lo que un arrastre debe borrar su estado activo.pointercancel.Evento submit
submit es un evento cancelable de formulario que se despacha durante la vía normal de envío después de que la validación automática lo permita.submit de un formulario maneja un envío válido añadiendo un elemento localmente y cancelando la navegación.F
Fase de captura
Fase de destino
eventPhase 2 mientras se ejecutan.Foco
document.activeElement.search.focus() dirige el siguiente carácter escrito al campo de búsqueda.Fondo inerte
G
Gestión del foco
Grupo de controles compuesto
I
Identidad del detector de eventos
Indicador de foco
:focus-visible muestra qué botón de la barra de herramientas recibirá la siguiente acción de teclado.Indicador de tecla modificadora
ctrlKey o metaKey junto con la letra S.Í
Índice de tabulación móvil (roving tabindex)
M
Máscara de bits de botones
buttons del evento.buttons 3, al combinar los indicadores 1 y 2.Método checkValidity
checkValidity() devuelve si se cumplen las restricciones y dispara eventos invalid para los fallos sin mostrar mensajes del navegador.Método closest
closest() comprueba un elemento y después sus nodos antecesores para encontrar la coincidencia más cercana con un selector; devuelve null si no existe ninguna.closest("button") encuentra el botón de acción que lo rodea.Método contains
contains() comprueba si un nodo es el propio elemento o uno de sus descendientes y devuelve un resultado booleano.list.contains(button) antes de permitir la acción del botón.Método preventDefault
preventDefault() cancela la acción predeterminada de un evento cancelable en un detector no pasivo sin detener su propagación.preventDefault() antes de añadir un elemento local sin navegar.Método reportValidity
reportValidity() comprueba las restricciones y también solicita al navegador que muestre mensajes de validación para los fallos no manejados.reportValidity() para ayudar al usuario a encontrar un campo que no pasa la validación.Método requestSubmit
requestSubmit() solicita el envío de un formulario mediante la vía normal de validación y del evento submit, sin disparar un clic en el botón.form.requestSubmit() para que sigan ejecutándose la validación y el detector de envío del formulario.Método stopImmediatePropagation
stopImmediatePropagation() detiene el recorrido del evento y también omite los detectores restantes registrados en el destino actual del evento.Método stopPropagation
stopPropagation() impide que un evento continúe hacia otros elementos y deja activos los demás detectores de su elemento actual.N
Nombre accesible
aria-label="Close" para indicar qué hace su símbolo.O
Objeto de evento
key del evento recibido para decidir si debe cerrar un panel.Opción once
once solicita la eliminación automática de ese registro después de su primera invocación para el tipo de evento seleccionado.once: true para el clic que lo descarta durante este periodo de actividad.P
Propagación
Propagación por burbujeo
bubbles es verdadero.input de un campo hijo se propaga por burbujeo hasta un detector registrado en el formulario que lo contiene.Propiedad cancelable
cancelable es un booleano que indica si un evento permite cancelar su acción predeterminada mediante preventDefault().input informa de una edición ya realizada, por lo que su propiedad cancelable es falsa.Propiedad checked
checked es el estado booleano que indica si una casilla de verificación o un botón de opción está seleccionado, independientemente de su valor de envío.checkbox.checked para decidir si muestra una sección opcional.Propiedad code
code identifica una posición física de tecla independientemente del carácter producido por la distribución actual del teclado.code cuando sus controles de movimiento deben permanecer en posiciones físicas fijas.Propiedad CSS touch-action
touch-action declara qué gestos táctiles del navegador se permiten cuando un gesto comienza en un elemento.touch-action: none en la superficie de arrastre reserva el movimiento de esa área para el arrastre.Propiedad currentTarget
currentTarget identifica el objeto cuyo detector se está ejecutando y se restablece a null cuando termina el despacho del evento.currentTarget permite que el código lea los datos de ese botón.Propiedad de manejo de eventos
onclick, que contiene una función de devolución de llamada de evento y la sustituye cuando se le asigna otra.button.onclick sustituye el manejador anterior de esa propiedad.Propiedad defaultPrevented
defaultPrevented es un booleano que registra si la acción predeterminada de un evento se ha cancelado correctamente.defaultPrevented antes de decidir cómo manejar un clic.Propiedad key
key indica el valor producido por una tecla, teniendo en cuenta la distribución del teclado y modificadores como Mayús.key de una letra de "a" a "A" mientras su posición permanece igual.Propiedad pointerId
pointerId identifica un puntero individual durante una interacción para que los contactos simultáneos puedan manejarse por separado.Propiedad relatedTarget
relatedTarget identifica el otro elemento implicado en una transición de foco, o es null cuando no hay ningún elemento disponible.focusout de un menú comprueba si el destino permanece dentro del menú.Propiedad target
target identifica el origen del evento, que puede ser un elemento anidado dentro del control cuya acción se está manejando.target del clic.Propiedad value
value expone como texto el valor actual de un control de formulario, incluidos los campos numéricos cuyo contenido aún necesita conversión."8" necesita conversión antes de que el código añada un incremento numérico.Propiedad valueAsNumber
valueAsNumber lee directamente el valor numérico de un campo compatible y devuelve NaN cuando no hay un valor numérico utilizable.NaN, que el código detecta con Number.isNaN().R
Referencia a función
openPanel registra la función; pasar openPanel() la llama durante la configuración.Restablecimiento del formulario
form.reset() restablece los valores iniciales de los campos del formulario.Resumen de errores
Rol ARIA
role="button" sigue necesitando código para la activación y una parada de tabulación adecuada.V
Validación de restricciones
Validez personalizada
setCustomValidity(), en la que un mensaje no vacío marca el control como no válido.Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.