Date Pickers Móviles: Por qué el Scroll Funciona Mejor y Cómo Crear Uno con CSS y JavaScript
¡Qué onda, desarrolladores! Bienvenidos nuevamente a 8devmx. Si has intentado reservar un vuelo, agendar una cita médica o simplemente registrar tu fecha de nacimiento desde tu teléfono celular, es muy probable que te hayas topado con la experiencia frustrante de los selectores de fecha (date pickers).
Durante años, el estándar en las páginas web ha sido mostrar un calendario en forma de cuadrícula mensual. Para una computadora de escritorio con pantalla grande y cursor de ratón, este modelo funciona muy bien. Sin embargo, cuando trasladamos ese mismo calendario a una pantalla táctil de 6 pulgadas, la experiencia de usuario suele volverse una pesadilla de toques accidentales y clics perdidos.
En este artículo analizaremos por qué los selectores de fecha basados en scroll (desplazamiento vertical) son mucho más eficientes en móviles y aprenderemos a construir un prototipo funcional paso a paso utilizando solo HTML, CSS moderno y JavaScript limpio.
El problema del calendario tradicional en pantallas táctiles
El selector de fecha tradicional se basa en la navegación mensual: ves un mes completo, buscas el día en la cuadrícula y, si necesitas cambiar de mes, presionas flechas diminutas situadas en la parte superior.
En teléfonos móviles, este patrón enfrenta serios inconvenientes:
- Áreas de toque demasiado pequeñas: Las flechas para cambiar de mes o año suelen medir apenas unos cuantos píxeles. En una pantalla táctil, esto provoca toques accidentales constantemente.
- Navegación lenta entre años: Si el usuario necesita ingresar una fecha distante (como su año de nacimiento hace 30 años), presionar la flecha de “mes anterior” decenas de veces resulta cansado e ineficiente.
- Uso deficiente del espacio: Las cuadrículas cuadradas no se adaptan bien a la orientación vertical de los smartphones.
Por qué la experiencia basada en Scroll gana en móviles
Los sistemas operativos móviles como iOS y Android descubrieron la solución hace mucho tiempo: las ruedas de desplazamiento (scroll wheels).
Al organizar la selección de fecha en columnas verticales de desplazamiento continuo (una para el día, otra para el mes y otra para el año), aprovechamos los gestos naturales del usuario:
- Gestos táctiles nativos: Deslizar el pulgar hacia arriba o hacia abajo es la interacción más natural en un smartphone.
- Mayor velocidad: Puedes recorrer décadas en un solo deslizamiento rápido (flick).
- Áreas de interacción más amplias: No necesitas acertarle a un número pequeño; basta con deslizar cualquier parte de la columna correspondiente.
¡Veamos cómo podemos replicar este patrón para la web usando tecnologías básicas!
Paso a paso: Creando un Scroll Date Picker con HTML, CSS y JS
Construiremos un selector de tres columnas (Día, Mes, Año) utilizando CSS Scroll Snap, una característica moderna de CSS que permite que el desplazamiento se detenga exactamente en el elemento central.
1. La Estructura HTML
Creamos un contenedor principal con tres columnas verticales. Cada columna contendrá una lista de opciones.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll Date Picker - 8devmx</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="picker-container">
<div class="picker-selection-highlight"></div>
<!-- Columna de Días -->
<div class="picker-column" id="col-days"></div>
<!-- Columna de Meses -->
<div class="picker-column" id="col-months"></div>
<!-- Columna de Años -->
<div class="picker-column" id="col-years"></div>
</div>
<div class="result">
Fecha seleccionada: <span id="selected-date">--/--/----</span>
</div>
<script src="app.js"></script>
</body>
</html>
2. Los Estilos CSS con Scroll Snap
El truco principal para que la rueda de desplazamiento se sienta suave e intuitiva está en las propiedades scroll-snap-type y scroll-snap-align.
/* style.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #0f172a;
color: #f8fafc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.picker-container {
position: relative;
display: flex;
width: 320px;
height: 200px;
background: #1e293b;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}
/* Resaltado central para el valor seleccionado */
.picker-selection-highlight {
position: absolute;
top: 80px;
left: 0;
width: 100%;
height: 40px;
background: rgba(56, 189, 248, 0.15);
border-top: 2px solid #38bdf8;
border-bottom: 2px solid #38bdf8;
pointer-events: none;
z-index: 2;
}
.picker-column {
flex: 1;
height: 100%;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scrollbar-width: none; /* Ocultar scrollbar en Firefox */
-ms-overflow-style: none; /* Ocultar scrollbar en IE/Edge */
}
.picker-column::-webkit-scrollbar {
display: none; /* Ocultar scrollbar en Chrome/Safari */
}
.picker-item {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
color: #94a3b8;
scroll-snap-align: center;
transition: color 0.2s;
}
/* Relleno superior e inferior para permitir centrado perfecto */
.picker-padding {
height: 80px;
}
.result {
margin-top: 20px;
font-size: 1.2rem;
font-weight: 600;
color: #38bdf8;
}
3. La Lógica JavaScript
Utilizaremos JavaScript para generar los números dinámicamente y calcular qué opción está alineada en el centro al hacer scroll.
// app.js
document.addEventListener('DOMContentLoaded', () => {
const colDays = document.getElementById('col-days');
const colMonths = document.getElementById('col-months');
const colYears = document.getElementById('col-years');
const selectedDateText = document.getElementById('selected-date');
const monthsNames = [
'Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'
];
// Rellenar elementos en cada columna
function populateColumn(container, start, end, formatFn = null) {
container.innerHTML = '<div class="picker-padding"></div>';
for (let i = start; i <= end; i++) {
const item = document.createElement('div');
item.classList.add('picker-item');
item.dataset.value = i;
item.textContent = formatFn ? formatFn(i) : i;
container.appendChild(item);
}
container.innerHTML += '<div class="picker-padding"></div>';
}
// Poblar días (1-31), meses (0-11) y años (1950-2026)
populateColumn(colDays, 1, 31, (val) => val.toString().padStart(2, '0'));
populateColumn(colMonths, 0, 11, (val) => monthsNames[val]);
populateColumn(colYears, 1950, 2026);
// Obtener el elemento centrado de una columna
function getSelectedValue(container) {
const itemHeight = 40;
const scrollTop = container.scrollTop;
const index = Math.round(scrollTop / itemHeight);
const items = container.querySelectorAll('.picker-item');
if (items[index]) {
return items[index].dataset.value;
}
return null;
}
// Actualizar la fecha global mostrada
function updateSelectedDate() {
const day = getSelectedValue(colDays);
const monthIndex = getSelectedValue(colMonths);
const year = getSelectedValue(colYears);
if (day && monthIndex !== null && year) {
const dayFormatted = day.toString().padStart(2, '0');
const monthFormatted = (parseInt(monthIndex) + 1).toString().padStart(2, '0');
selectedDateText.textContent = `${dayFormatted}/${monthFormatted}/${year}`;
}
}
// Escuchar el evento scroll con un pequeño retardo (debounce)
[colDays, colMonths, colYears].forEach(column => {
column.addEventListener('scroll', () => {
updateSelectedDate();
});
});
// Inicializar fecha
updateSelectedDate();
});
Errores comunes al diseñar selectores de fecha móviles
- Forzar selectores de escritorio en móviles: Importar componentes pesados en React o jQuery pensados para monitores de 27 pulgadas suele arruinar la experiencia en celulares Android o iOS económicos.
- No ajustar los días según el mes: Mostrar el “31 de Febrero” es un error muy común. En un sistema de producción, debes escuchar el cambio de mes/año y recalcular cuántos días tiene ese mes específico.
- Olvidar la accesibilidad (A11y): Asegúrate de incluir etiquetas semánticas o atributos
aria-labelpara que las personas que utilizan lectores de pantalla puedan seleccionar sus fechas sin complicaciones.
Buenas prácticas para tu proyecto
- Aprovecha los componentes nativos cuando sea posible: En formularios sencillos, el tag HTML
<input type="date">dispara el selector nativo del sistema operativo del teléfono, el cual ya está optimizado para esa pantalla. - Usa CSS Scroll Snap: Evita procesar la física del movimiento manualmente con JavaScript. CSS Scroll Snap es procesado directamente por la GPU del dispositivo, garantizando 60 cuadros por segundo.
- Da retroalimentación visual clara: Utiliza cambios de color o líneas divisorias para indicar con precisión qué elemento está seleccionado.
Ejercicio práctico para ti
¿Quieres poner a prueba lo aprendido? Intenta agregar la siguiente funcionalidad al código del tutorial:
- Desafío: Crea una función que verifique si el año seleccionado es bisiesto y actualice la columna de días para que Febrero muestre 29 días en lugar de 28.
Pista: Un año es bisiesto si es divisible entre 4, excepto si es divisible entre 100, a menos que también sea divisible entre 400 ((year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)).
Fuente original
Este artículo fue inspirado en el análisis técnico sobre interacción móvil publicado en Dev.to por rolldate.
¿Tienes alguna duda sobre CSS Scroll Snap o JavaScript? ¡Déjanos un comentario y no olvides compartir este tutorial con tus compas de la universidad o del trabajo! Nos vemos en el próximo post de 8devmx.