WA4 · Ejercicios de movimiento, accesibilidad y rendimiento¶
Los ejercicios WA4.1 a WA4.4 son de JavaScript sin DOM: escríbelos en un .js/.mjs y ejecútalos con node; tu salida debe ser idéntica al resultado esperado, obtenido ejecutando la solución modelo con Node de verdad. Los WA4.5 y WA4.6 son de HTML/DOM: se hacen en una página y se comprueban en el navegador. Los marcados con ⭐ son más difíciles.
Ejercicio WA4.1¶
Calcular el contraste. Completa contraste (la función luminancia ya está hecha).
function luminancia(hex) {
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255)
.map(c => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// contraste(a, b): la proporción de contraste WCAG entre dos colores #rrggbb, SIEMPRE mayor o igual que 1
// (el color más claro va en el numerador): (Lclaro + 0.05) / (Loscuro + 0.05)
function contraste(a, b) {
// ...
}
Para comprobarlo:
console.log(contraste("#000000", "#ffffff").toFixed(2));
console.log(contraste("#ffffff", "#000000").toFixed(2));
console.log(contraste("#777777", "#ffffff").toFixed(2));
console.log(contraste("#ffffff", "#2563eb").toFixed(2));
Resultado esperado (ejecutado con Node):
21.00
21.00
4.48
5.17
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
function luminancia(hex) {
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255)
.map(c => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// ... (resto de la solución bloqueado)Ejercicio WA4.2¶
¿Pasa la prueba? Escribe nivel.
// (usa la función contraste del ejercicio anterior)
// nivel(texto, fondo, grande = false) devuelve "AAA", "AA" o "suspenso":
// texto normal: AAA si contraste >= 7, AA si >= 4.5
// texto grande: AAA si contraste >= 4.5, AA si >= 3
function nivel(texto, fondo, grande = false) {
// ...
}
Para comprobarlo:
console.log(nivel("#000000", "#ffffff"));
console.log(nivel("#767676", "#ffffff"));
console.log(nivel("#777777", "#ffffff"));
console.log(nivel("#777777", "#ffffff", true));
console.log(nivel("#aaaaaa", "#ffffff", true));
Resultado esperado (ejecutado con Node):
AAA
AA
suspenso
AA
suspenso
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
function luminancia(hex) {
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255)
.map(c => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// ... (resto de la solución bloqueado)Ejercicio WA4.3¶
debounce. Escríbelo desde cero.
// debounce(fn, ms): devuelve una función que, al llamarla, espera `ms` milisegundos SIN nuevas llamadas
// y entonces ejecuta fn UNA vez, con los argumentos de la ÚLTIMA llamada.
function debounce(fn, ms) {
// ...
}
Para comprobarlo:
const dormir = ms => new Promise(r => setTimeout(r, ms));
const vistos = [];
const guardar = debounce(valor => vistos.push(valor), 60);
for (const v of ["a", "b", "c"]) { guardar(v); await dormir(10); }
await dormir(150);
guardar("d");
await dormir(10);
guardar("e");
await dormir(150);
console.log(vistos);
Resultado esperado (ejecutado con Node):
[ 'c', 'e' ]
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
function debounce(fn, ms) {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), ms);
// ... (resto de la solución bloqueado)Ejercicio WA4.4¶
throttle. Escríbelo desde cero, con un reloj inyectado para poder probarlo sin esperar.
// throttle(fn, ms, ahora = Date.now): devuelve una función que ejecuta fn como mucho UNA vez cada `ms` ms:
// la primera llamada pasa; las siguientes se ignoran hasta que hayan pasado `ms` desde la última que SÍ pasó.
// Devuelve lo que devuelve fn cuando se ejecuta, y undefined cuando se ignora la llamada.
function throttle(fn, ms, ahora = Date.now) {
// ...
}
Para comprobarlo:
let reloj = 0;
const f = throttle(x => "pasó " + x, 100, () => reloj);
for (const [t, x] of [[0, "A"], [50, "B"], [99, "C"], [100, "D"], [150, "E"], [199, "F"], [200, "G"]]) {
reloj = t;
console.log(t, "→", f(x));
}
Resultado esperado (ejecutado con Node):
0 → pasó A
50 → undefined
99 → undefined
100 → pasó D
150 → undefined
199 → undefined
200 → pasó G
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
function throttle(fn, ms, ahora = Date.now) {
let ultima = -Infinity;
return (...args) => {
const t = ahora();
if (t - ultima >= ms) {
// ... (resto de la solución bloqueado)Ejercicio WA4.5¶
Un menú desplegable accesible. (DOM: se comprueba en el navegador.) Con este HTML, escribe el JavaScript para que:
- el botón abra y cierre el menú, manteniendo
aria-expandedsincronizado con su estado (el menú se oculta con el atributohidden); - Escape cierre el menú y devuelva el foco al botón;
- un clic fuera del menú lo cierre.
Comprueba todo solo con el teclado (Tab, Intro, Escape).
<button id="boton" aria-expanded="false" aria-controls="menu">Menú</button>
<ul id="menu" hidden>
<li><a href="#inicio">Inicio</a></li>
<li><a href="#cursos">Cursos</a></li>
<li><a href="#contacto">Contacto</a></li>
</ul>
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
<button id="boton" aria-expanded="false" aria-controls="menu">Menú</button>
<ul id="menu" hidden>
<li><a href="#inicio">Inicio</a></li>
<li><a href="#cursos">Cursos</a></li>
<li><a href="#contacto">Contacto</a></li>
</ul>
<!-- ... (resto de la solución bloqueado) -->Ejercicio WA4.6¶
⭐ Una imagen responsiva. (HTML: se comprueba en las herramientas del navegador.) Escribe el HTML de una imagen de portada que:
- ofrezca tres anchos (
480w,960wy1600w) consrcset, y que ocupe toda la pantalla en móviles y la mitad a partir de 60 rem (sizes), - ofrezca el formato WebP con
<picture>y JPG como alternativa, - reserve su hueco (proporción 16:9, 1600 × 900) para evitar saltos de diseño,
- tenga un texto alternativo descriptivo,
- sea la imagen principal de la página: no debe cargarse de forma perezosa y debe tener prioridad alta.
(Los archivos no existen: lo que se comprueba es la estructura. En la pestaña «Red» de las herramientas del navegador verás qué archivos intenta pedir.)
Solución modelo (bloqueada)
🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:
<picture>
<source type="image/webp"
srcset="portada-480.webp 480w, portada-960.webp 960w, portada-1600.webp 1600w"
sizes="(min-width: 60rem) 50vw, 100vw">
<img src="portada-960.jpg"
srcset="portada-480.jpg 480w, portada-960.jpg 960w, portada-1600.jpg 1600w"
<!-- ... (resto de la solución bloqueado) -->