en HTML5

Input type range: slider bar

Hoy os traigo el que probablemente es mi HTML5 input type favorito, el input type range, que nos va a permitir crear un selector de rangos mediante el uso de un deslizador que constará de una barra de tracking y un manipulador que será el que nos permita deslizarnos entre los valores.

Hasta ahora había publicado otros input type como el type email y el type search, muy útiles ambos considerando que son probablemente los más comunes. Pero hoy me quiero salir un poco de lo común y enseñaros un input que no parece un input, es decir un campo de formulario que adquiere una apariencia diferente. De hecho el único además del type button y type color, que no permite la entrada de caracteres, type range.

esquema de la estructura de input type range: range-progress, range-track y range-thumb

Funcionalidad del range slider

Su funcionalidad no es compleja, pero voy a definirla brevemente pues la que nos brinda HTML5 es algo más limitada que la de otros selectores de rangos hechos con javascript.

La funcionalidad nativa del input type range de HTML5 básicamente permite definir dos valores: mínimo y máximo que se ubicarán en los extremos de la barra y cuyos valores intermedios podrán, opcionalmente, ir definidos por segmentos.

Cambiando la posición del handler (manipulador deslizante), podremos seleccionar un valor de los comprendidos dentro del rango global, definido por los atributos min y max, que será el valor máximo del rango, mientras que el valor mínimo permanecerá fijo.

Type range soporta onChange y onInput, pero su desempeño en los diferentes navegadores es inconsistente. Lo vemos más en detalle en el apartado de usabilidad y accesibilidad.

HTML Slider básico

Atributos min max

Define el rango mediante dos valores numéricos que por defecto, en caso de no ser definidos, serán 0 y 100.

<input type="range" min="0" max="100" />

Atributo step

Determinará el número de segmentos o intervalos y, por lo tanto, de valores posibles a seleccionar dentro del rango definido, a los que habrá que sumar el 0 ó valor definido por el atributo min.  Un valor de 20 dentro de un rango de 0 a 100, devolverá 5 segmentos con 6 valores posibles (0,20,40,60,80,100).

Acepta cualquier valor positivo (incluidos decimales) y la etiqueta any, que elimina los intervalos, es decir que acepta cualquier valor dentro del rango definido por min max. El valor por defecto de step es 1.

<input type="range" min="0" max="100" step="20" />

Atributo value

Finalmente este atributo definirá un valor por defecto o inicial, que en su defecto será el valor intermedio del rango o el inicio del siguiente segmento a partir del centro, en caso de ser impares (como al usar step=20).

<input type="range" min="0" max="100" step="10" value="0" />

HTML Slider con datalist

Vamos un poco más allá y personalizar los segmentos de nuestro rango global

Atributo list de input range

A los atributos definidos más arriba podemos añadir list que nos permite enlazar ciertos input, en este caso type range, con una datalist.

<input type="range" min="0" max="100" step="10" value="0" list="mydata" />

Elemento datalist

Los segmentos del slider se seguirán generando en base a los atributos step y min max, pero ahora podremos añadir marcas que no tienen por qué ser homogéneas ni coincidir con los intervalos definidos por step.

<datalist id="mydata">
  <option value="1"></option>
  <option value="2"></option>
  <option value="3"></option>
  <option value="5"></option>
  <option value="8"></option>
  <option value="13"></option>
</datalist>

Aquí tenéis unos ejemplos:

See the Pen
Slider bar básica
by Daniel Abril (@elcssar)
on CodePen.

Personalizando el slider con CSS3

DOM de type range

Antes de atacar los estilos, vamos a desvelar el truco detrás de la magia. ¿Qué hay detrás de este input que no parece un campo de formulario?, ¿de dónde salen todos esos elementos extra que nos permiten introducir un valor sin usar el teclado, sólo con el ratón? La respuesta es sencilla, los genera el navegador al vuelo igual que nosotros generamos pseudo-elementos con ::before y ::after, de hecho tiene sus propios pseudo-selectores que vamos a ver en un momento junto con los pseudo-elementos del DOM a los que hacen referencia y que, generalmente, están ocultos.

Navegadores Webkit (Chrome, Safari, Opera, Edge…)

Este es el DOM que generan los Webkit

DOM de input type="range" en Webkit

Y estos los pseudoselectores:

::-webkit-slider-runnable-track
::-webkit-slider-thumb
/*No, no hay progress O_o, lo siento*/

Firefox

Este es el DOM que generan los Firefox

DOM de input type="range" en Firefox

Y estos los pseudo-selectores en el mismo orden que se generan los <div>:

::-moz-range-track
::-moz-range-progress
/*Sorpresa, sí hay progress, lo siento de nuevo porque ahora unos tienen y otros no, qué faena*/
::-moz-range-thumb

Internet Explorer

El DOM de IE es tan complejo que no os lo voy a poner. No aporta nada relevante al objetivo de este artículo. Especialmente cuando ya se puede considerar un navegador obsoleto.

—¿En serio?
—Sí, en serio. Os estoy haciendo un favor, creedme 😉

Bueno, venga va, os enseño los pseudo-selectores:

::-ms-track
::-ms-thumb
::-ms-fill-upper /*Sí, es el progress*/

Estilos CSS del slider bar

Ahora que ya conocemos su estructura y funcionamiento, podemos atacar los estilos para personalizar y homogeneizar nuestros type range y que tengan el mismo aspecto en todos los navegadores. Aunque cada vez son más parecidos.

Aspecto de type range en Chrome, Firefox y Edge en junio de 2021

Imagino que en este momento todos os estaréis preguntando qué pasa con ese ::range-progress para el que la mayoría de navegadores (los Webkit) no aportan un pseudo-selector para controlarlo por CSS. Don’t PANIC!!, veamos primero los fáciles.

Los elementos que conforman el slider no son otra cosa que pseudo-elementos div con identificadores, por lo tanto, gracias a los pseudo-selectores que ya hemos visto podremos modificarlos, por ejemplo, como cualquier ::before.

Una peculiaridad es que no podemos encadenarlos separados por comas para aplicarles a cada tipo el mismo valor en todos los navegadores con un único set de reglas. Por eso para no tener que trabajar con un código enorme y repetitivo uso @mixin de SCSS.

Antes de ir con cada parte tenemos que preparar algunas variables y deshacernos de los estilos del navegador:

$spacing: 12px;
$module: 48px;
* {
  box-sizing: border-box;
}
input[type=range] {
  /*Damos unos estilos básicos*/
  width: 100%;
  /*Eliminamos la apariencia por defecto: -webkit-appearance: slider-horizontal;*/
  -webkit-appearance: none; 
  /*Eliminamos el borde que seguramente heredaría de los input*/
  border: none; 
  /*Eliminamos el outline que por defecto suele aparecer al activarse*/
  outline: none; 

  /*Aquí añadiremos todos los pseudo-selectores*/
  &::pseudo-selector {@include mixin-name}
}

Tracking bar

Creamos el @mixin con los estilos básicos

@mixin track() {
  height: 2px;
  padding: 0;
  cursor: pointer;
  background: #ccc;
}

Y añadimos dentro de [type=range]{} los ::pseudo-selectores con los @includes:

&::-webkit-slider-runnable-track { @include track }
&::-moz-range-track { @include track }
&::-ms-track { @include track }

Thumb

De nuevo primero el @mixin con los estilos básicos

@mixin thumb() { 
  box-sizing: border-box;/*Esta regla es sólo para FF, Webkit ya la aplica por defecto*/
  -webkit-appearance: none; /*Aquí no hereda del input por lo que hay que recordárselo*/
  /*a continuación lo estilos que queramos que tenga nuestro handler*/
  width: $module/2;
  height: $module/2;
  margin-top: -$spacing;
  border: $spacing/2 solid #eee;
  border-radius: 50%;
  background: #999;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5)
}

Y añadimos a continuación los ::pseudo-selectores con los @includes:

&::-webkit-slider-thumb { @include thumb }
&::-moz-range-thumb { @include thumb }
&::-ms-thumb { @include thumb }

Progress bar

Esta es la parte divertida. Ya hemos visto que sólo Firefox e IE (hoy un navegador obsoleto), disponen de un pseudo-selector para la barra de progreso. Como siempre creamos el @mixin:

@mixin progress() {
  background: #99F;
  height: 2px;
  /*también podéis cambiar border, etc...*/
}

Y añadimos a continuación los ::pseudo-selectores con los @includes:

&::-moz-range-progress { @include progress }
&::-ms-fill-upper { @include progress }

Listo, ya tenemos personalizada nuestra barra de progresso para estos navegadores, pero ¿y para el otro 90%? Aquí tendremos que hacer uso de variables CSS, calc() y un pequeño JS.

HTML
<form oninput="changeVar(rangeField.value,rangeField.min, rangeField.max)">
<!--on input pasamos a la función los valores que necesitaremos para operar-->
<label class="sliderBar">Custom slider
<input id="rangeField" type="range" min="0" max="100" value="50" step="10">
<output id="output" value="0">0</output>
</label>
jQuery
function changeVar(value, min, max){
  var range = max - min; //Obtenemos el rango total. Al restar siempre obtendremos valores de rango (%) positivos, aún cuando trabajemos con valores inferiores a 0. Luego lo veremos más en detalle con datalist.

  var progressW = (value-min)/range*100 +'%'; //Guardamos en una variable el % de progreso que obtenemos mediante una regla de tres donde el rango es el valor que equivale al 100% y la parte en azul sería el valor actual cuyo % queremos obtener. La razón de restar el mínimo al valor es obtener un valor relativo al rango global, pues la escala no tiene por qué comenzar en 0, puede ser + ó -.

  $('#rangeField').css('--progressW', progressW); //Finalmente establecemos el valor de la variable CSS que nos permitirá determinar la longitud de la barra de progreso
}
SCSS
:root {
  --progressW: 50%; /*En primer lugar creamos el valor por defecto para la variable a nivel de root*/
}
[type=range]::-webkit-slider-runnable-track {
  background: linear-gradient(to right, #99F 0%, #99F var(--progressW), #ccc var(--progressW), #ccc 100%);
/*Y para terminar sobrescribimos nuestro background fuera del @mixin por un degradado parametrizado con la variable que nos da el % actual de progress*/
}

See the Pen
Slider bar custom + output
by Daniel Abril (@elcssar)
on CodePen.

Datalist

Ya conocemos la estructura. De sus estilos, decir que por defecto los valores de datalist se visualizan en forma de pequeñas marcas (excepto en FF), que son distribuidas en relación a los valores del rango del input; una vez eliminado su appearance, datalist sólo será visible si sus options contienen texto o si los personalizamos y distribuimos con CSS. No voy a extenderme más, os dejo el ejemplo que creo que se explica por sí mismo y de paso veis la funcionalidad del ::progress con valores negativos.

See the Pen
Slider bar custom + datalist
by Daniel Abril (@elcssar)
on CodePen.

Usabilidad y accesibilidad

Soporte en navegadores

El soporte funcional de type range es total. Aunque dispongamos de diferentes niveles de control de sus estilos.

Interacción

Controles

La accesibilidad a través del teclado se realiza mediante cualquiera de las siguientes opciones:

  • Flechas horizontales [←] [→]
  • Flechas verticales [↑][↓] (Sólo si no usas lectores de pantalla, pues capturan estas teclas para navegar entre elementos)
  • Avance y retroceso de página [▲▼]

Eventos input y change

Tanto si usas las teclas mencionadas sobre estas líneas como el ratón sobre input range, tenemos dos formas de capturar el cambio, usando onInput y/o onChange. En el ejemplo que hemos visto para explicar la personalización de los CSS, he incluido un output cuyo valor era tomado del valor del type range onChange y también hemos usado el onInput para actualizar el estado de la barra de progreso. El comportamiento, sin embargo, no es idéntico en todos los navegadores:

Webkits (Chrome, Edge)

OnChange cambia sólo cuando soltamos el manipulador, mientras que la barra de progreso se va actualizando de forma instantánea con onInput. Con las flechas es en ambos eventos inmediato ya que se disparan a la vez.

Firefox

Actualmente funciona idéntico a los Webkits, aunque en versiones antiguas podrías encontrar un pequeño bug al usar las flechas.

Internet Explorer 11

OnChange funciona exactamente igual que onInput en el resto de navegadores. Paradójicamente, onInput ni siquiera es reconocido usado con input range. Aunque en su forma nativa, a diferencia del resto de navegadores, muestra un tooltip con el valor sobre el handler (::range-thumb) al arrastrarlo con el cursor.

Llegados a este punto quizás os preocupe usar onInput para cambiar el ::range-progress, pero si os fijáis el ::range-track en IE y FF es de un color sólido, así que el problema no es tal. En cuanto a su uso para otros fines, recomiendo onChange, a menos que no requiráis dar compatibilidad a IE11 o inferiores.

Por su lado, el uso del teclado tampoco dispara el evento onInput, ni muestra el tooltip.

WAI-ARIA y lectores de pantalla

Role slider

  • El rol nativo de type range es slider, por lo que no es necesario declarar role=»slider».
  • Todos los lectores de pantalla lo anuncian, sea como range o slider
  • Tanto Voice Access (Android) como Windows Speech Recognition permiten el control por voz en Chrome. No así otros como Dragon (Chrome en Android) o Voice Control (Safari en iOS y MacOS).

Label

  • También la etiqueta label es anunciada en Chrome por Voice Access y Windows Speech, así como en Safari, pero sólo en iOS.
  • En caso de no incluirla se debería usar en su lugar aria-label en el input.

Valores (min, max, current value)

  • Los valores de los atributos min y max no son anunciados. Tampoco usando sus versiones aria-*, que dicho sea de paso, no son necesarios usando type range.
  • El valor actual (current value) sí es anunciado tanto al tomar foco como al cambiar.
  • Además la mayoría de controles de voz tienen control total para el incremento y decremento del valor.

Atajos de voz

A excepción de NVDA (Chrome y FF) y Orca (FF), el resto de lectores de pantalla proveen de atajos de voz.

Instrucciones

Para cambiar los valores podemos usar las teclas detalladas en la sección anterior. Sin embargo, esto puede no ser obvio para todos los usuarios. Si dispusiésemos de un texto donde se resumen esos comandos, podríamos usar aria-describedby para hacer referencia a él, sin embargo, este no es probablemente el caso más común, por lo que aquí nuestro aliado será title, cuya función puede ser tanto actuar como nombre accesible, como descriptivo si ya disponemos de un label o aria-label.

<input type="range" aria-label="Precio" title="Use las flechas derecha e izquierda para incrementar y disminuir, respectivamente, el valor del campo">

Conclusiones

Type range provee una forma fácil y accesible de seleccionar rangos con poco esfuerzo, muy compatible con pantallas táctiles. Además su aspecto es cada vez más parecido en todos los navegadores, pero si no es suficiente para tus requerimientos, los navegadores disponen de opciones para su personalización.

Espero que os sea útil y lo uséis en vuestros proyectos.

Escribe un comentario

Comentario

    • Estimado Marcos, disculpa la respuesta tardía.
      Sí es posible hacerlo, lamentablemente no con un único input y tampoco sin javascript.
      Dejo por aquí un codepen bastante sencillo que os permitirá crear un slider combinando dos input type range.