Input type range y javascript

facebook-svg gplus-svg twitter-svg

Que queremos

Queremos crear varios controles deslizantes ( sliders ), cada uno con sus características, y queremos poder visualizar el valor de éstos en cada momento.

Que necesitamos

Primero tenemos que decidir donde poner los nuevos controles deslizantes:
En el HTML tenemos dos divs donde queremos colocar los "sliders"


<div class="inputDiv i1" ></div>
<div class="inputDiv i2" ></div>

En JavaScript identificamos estos dos elementos ( .inputDiv.i1 y .inputDiv.i2 ) como elementos padre:

var elementoPadre1 = document.querySelector(".inputDiv.i1");
var elementoPadre2 = document.querySelector(".inputDiv.i2");

Como que tendremos varios controles deslizantes, para poder manipularlos en grupo, creamos un array donde guardar los nuevos objetos creados.

var inputsRy = [];

Un elemento input type = "range" tiene este aspecto en HTML:


<input type="range" value="35" min="0" max="100" autocomplete="off" step="1">

Así que si creamos un nuevo objeto input necesitamos una propiedad que contenga todos estos atributos:

function Input() {
    this.att = {};
    this.att.type = "range";
    this.att.value = 35;
    this.att.min = 0;
    this.att.max = 100;
    this.att.autocomplete = "off";
    this.att.step = "1";
  }

Asimismo necesitamos poder guardar una referencia hacia el elemento input ( this.input ) que vamos a crear, y otra referencia hacia el elemento output ( this.output ), donde el valor del input value aparece en pantalla.

function Input() {
    this.att = {};
    this.att.type = "range";
    this.att.value = 35;
    this.att.min = 0;
    this.att.max = 100;
    this.att.autocomplete = "off";
    this.att.step = "1";
    this.input;
    this.output;
  }

Crear un nuevo objeto input

También tenemos que escribir una función que crea un nuevo elemento <input> y el div .output.

   this.crear = function(elementoPadre) {
      // crea un nuevo elemento input
      this.input = document.createElement("input");
      // para cada propiedad del objeto att
      for (var name in this.att) {
        if (this.att.hasOwnProperty(name)) {
          // establece un nuevo atributo del elemento input
          this.input.setAttribute(name, this.att[name]);
        }
      }
      // crea un nuevo elemento div
      this.output = document.createElement("div");
      // establece el valor del atributo class del nuevo div
      this.output.setAttribute("class","output");
      // y el contenido (innerHTML) de este
      this.output.innerHTML = this.att.value;
      // inserta los dos elementos creados al final  del elemento padre 
      elementoPadre.appendChild(this.input);
      elementoPadre.appendChild(this.output);
    }

La función actualizar

Finalmente necesitamos una función que actualice el contenido ( innerHTML ) del div .output, y el valor de this.att.value.

   
this.actualizar = function(){ 
   this.output.innerHTML = this.input.value;
   this.att.value = this.input.value;
}

Crear un nuevo elemento input

Ahora crear un nuevo "slider" es fácil. Solo necesitamos crear un nuevo objeto Input

var i = new Input();

Para crear un nuevo slider llamamos la función crear

i.crear(elementoPadre1);

Y guardamos el nuevo objeto input en un array.

inputsRy.push(i);

También podemos modificar el valor de algunos atributos del input de esta manera:

var i2 = new Input();
i2.att.value = 70;
i2.att.min = 20;
i2.att.max = 120;
i2.crear(elementoPadre2);
inputsRy.push(i2);

El evento input

El evento input se dispara en seguida que el valor de un elemento <input> o <textarea> cambia. A continuación aprovechamos este evento para cambiar el valor que aparece en cada elemento .output, y como que tenemos un array de controles deslizantes con sus elementos .output, utilizamos un bucle for ( for loop ) para iterar sobre este array. Y cada vez que se dispara el evento input para uno de este sliders actualizamos el HTML del .output.

for (var n = 0; n < inputsRy.length; n++) {
    (function(n) {
      inputsRy[n].input.addEventListener("input", function() {
        inputsRy[n].actualizar();
      }, false)
    }(n));
  }

Ahora al arrastrar los controles deslizantes, el valor que aparece dentro del elemento .output esta siendo actualizado según corresponda.

Vea este ejemplo en codepen:

See the Pen Input type range object by Gabi (@enxaneta) on CodePen.

Lea más acerca de addEventListener

Actualizar el CSS

Podemos mejorar todo esto dando un color diferente al "track" ( la barra de desplazamiento ) antes y después del "thumb" ( el botón o el control de la barra de desplazamiento ).

Para dar estilos diferentes a las varias partes del "track" el Internet Explorer nos lo pone muy fácil:

Para la parte baja ( lower ), antes del botón:

input[type=range]::-ms-fill-lower {
    background-color: HotPink
}

Para la parte alta ( upper ), después del botón:

input[type=range]::-ms-fill-upper {
    background-color: black;
}

Para los demás navegadores lo tenemos más difícil. Básicamente podemos utilizar para la barra de dezplazamiento un gradiente lineal que va de "HotPink" a "black" por ejemplo. Sabemos que un gradiente de color es una transición suave y progresiva entre dos o más colores. No tiene por que ser así. Podemos conseguir límites tajantes utilizando color-stops situados muy cerca el uno del otro. Es lo que haremos a continuación.
Si por ejemplo el "thumb" se encuentra justo en el medio del slider ( al 50% ) tendremos que poner estas reglas en el CSS:

#id::-webkit-slider-runnable-track{ 
  background-image:-webkit-linear-gradient(left, HotPink 50%,Black 50%);
  }
#id::-moz-range-track{ 
  background-image:-moz-linear-gradient(left, HotPink 50%,Black 50%)
  }

El problema es que tendremos que actualizar esto cada vez que se dispara el evento input ( cada vez que alguien mueve el thumb o sea el botón del slider ). La solución es crear un nuevo elemento <style> en el <head> del documento, y manipular el contenido de este vía JavaScript.

// una nueva hoha de estilo
var nuevaHojaDeEstilo = document.createElement("style");
document.head.appendChild(nuevaHojaDeEstilo);
// una cadena de texto donde guardar los estilos
var HojaCSS = "";

Ahora viene lo importante. Para manipular los elementos <style> tenemos que escribir un nuevo método de Input: el método CSSstyle(). Este método establece las nuevas reglas CSS de cada slider ( this.style )

this.CSSstyle = function() {
      // calcula la posición del thumb en porcentajes
      this.porcentaje = ((this.att.value - this.att.min) / this.interval) * 100;
      // establece las nuevas reglas CSS,
      // prácticamente las mismas reglas de arriba, 
      // donde hemos reemplazado los elementos destacados en rojo con variables
      this.style = "#" + this.att.id + "::-webkit-slider-runnable-track{ background-image:-webkit-linear-gradient(left, HotPink " + this.porcentaje + "%, black " + this.porcentaje + "%)}";
      this.style += "#" + this.att.id + "::-moz-range-track{ background-image:-moz-linear-gradient(left, HotPink " + this.porcentaje + "%, black " + this.porcentaje + "%)}";
}

Llamamos el nuevo método CSSstyle() al final del método crear() para establecer los estilos iniciales de los sliders.

this.crear = function(elementoPadre) {
    this.input = document.createElement("input");
    this.output = document.createElement("div");
    this.output.innerHTML = this.att.value;
    this.output.setAttribute("class", "output");
    for (var name in this.att) {
      if (this.att.hasOwnProperty(name)) {
        this.input.setAttribute(name, this.att[name]);
      }
    }
    elementoPadre.appendChild(this.input);
    elementoPadre.appendChild(this.output);
    this.CSSstyle()
  }

También lo llamamos al final del método actualizar(). De esta manera cada vez que cambia la posición del thumb actualizamos también los estilos del track ( la barra de dezplazamiento ).

this.actualizar = function() {
      this.output.innerHTML = this.input.value;
      this.att.value = this.input.value;
      this.CSSstyle();
}

Casi lo tenemos. Lo que queda por hacer es escribir una nueva función que pone juntas todas las nuevas reglas CSS en una sola cadena de texto y modifica el contenido ( textContent ) de la nuevaHojaDeEstilo.

function actualizarCSS() {
  // una cadena de texto donde guardar los estilos
  var HojaCSS = "";
  for (var i = 0; i < inputsRy.length; i++) {
    HojaCSS += inputsRy[i].style;
  }
  nuevaHojaDeEstilo.textContent = HojaCSS;
}

for (var n = 0; n < inputsRy.length; n++) {

  (function(n) {
    inputsRy[n].input.addEventListener("input", function() {
      inputsRy[n].actualizar();
      actualizarCSS();
    }, false);
  }(n));
}

Vea este ejemplo en codepen:

See the Pen Input type range object #3 by Gabi (@enxaneta) on CodePen.

Veamos otra posibilidad

En el CSS podemos establecer como imagen de fondo un gradiente lineal, por ejemplo:

background-image:linear-gradient(90deg,red 0%,yellow 300px);

Y después podemos utilizar la propiedad background-size  para especificar el tamaño de la imagen de fondo. Si por ejemplo el "thumb" se encuentra justo en el medio del slider ( al 50% ) tendremos que poner estas reglas en el CSS:

#id::-webkit-slider-runnable-track{ background-size:50% 100%;}
#id::-moz-range-track{ background-size:50% 100%}

Ahora podemos utilizar esta idea y reescribir el método CSSstyle(). Este método establece las nuevas reglas CSS de cada slider ( this.style )

this.CSSstyle = function() {
// calcula la posición del thumb en porcentajes
this.porcentaje = ((this.att.value - this.att.min) / this.interval) * 100;
// establece las nuevas reglas CSS
// prácticamente las mismas reglas de arriba, 
// donde hemos reemplazado los elementos destacados en rojo con variables
this.style = "#" + this.att.id + "::-webkit-slider-runnable-track{ background-size:" + this.porcentaje + "% 100%;}";
this.style += "#" + this.att.id + "::-moz-range-track{ background-size:" + this.porcentaje + "% 100%}";
}

Y ya está. Todo lo demás queda como en el ejemplo anterior.

Vealo en codepen:

See the Pen Input type range object #4 by Gabi (@enxaneta) on CodePen.