[JS|CSS] Haz un Slider básico con unas pocas líneas de código (No jQuery needed)

  • 1 Respuestas
  • 2459 Vistas

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado Gus Garsaky

  • *
  • Underc0der
  • Mensajes: 93
  • Actividad:
    0%
  • Reputación -1
  • Skype: gus.garsaky
    • Ver Perfil
Siguiendo con estos pequeños artículos de front end development, hoy vamos a realizar un slider utilizando JavaScript y un poco de CSS.

(Click en la imagen para ver el demo)

Citar
Todo el código fuente está en Github: https://github.com/Gusgarsaky/lightslider
QUÉ NECESITAMOS

  • CDN de fontawesome
  • normalize
  • prefixfree (opcional)
ESTRUCTURA

La estructura del proyecto será la siguiente:


MARCADO HTML

El marcado HTML es realmente muy sencillo. Solo necesitamos crear un wrapper con dos botones, los cuales serán anterior y siguiente:

Código: (html5) [Seleccionar]
<section class="slider" data-img-folder="img/">
    <button class="prevBtn"><i class="fa fa-2x fa-chevron-left"></i></button>
    <button class="nextBtn"><i class="fa fa-2x fa-chevron-right"></i></button>
    <img src="img/img1.jpg" alt="Image one" class="img" style="right: 0%"/>
</section>

Como vemos, el slider será el container y tendrá 2 botones que tienen un ícono (font awesome), que es una flecha en cada dirección. Además, por defecto  añadimos una imagen. El atributo data-img-folder contiene la ruta del folder que contiene las imágenes, en mi caso, img. Ahora pasemos a darle look and feel.

CSS

Código: (css) [Seleccionar]
html {
  box-sizing: border-box;
}
*, *::before, *::after {
  box-sizing: inherit;
}
.slider {
  border-radius: 4px;
  box-shadow: 0 15px 25px rgba(0,0,0,.15),
    0 25px 60px rgba(0,0,0,.25);
  display: block;
  height: 360px;
  margin: 30px auto;
  overflow: hidden;
  position: relative;
  width: 650px;
}

El slider tendrá unas dimensiones de 650px x 360px. La posición será relativa para que los botones se puedan ubicar bien. Tiene una sombra y un overflow: hidden. El overflow: hidden lo que hará es ocultar cuando su contenido se desborde. Esto lo hacemos así, porque las imágenes estarán a la derecha del slider, y con una suave transición, se mueven hacia la izquierda ocupando el tamaño del slider o viceversa (izquierda a derecha y derecha a izquierda).

Las imágenes tendrán unos estilos básicos:

Código: (css) [Seleccionar]
.slider .img {
  display: block;
  height: 100%;
  position: absolute;
  transition: all .6s ease-in-out;
  width: 100%;
}

Como ya habíamos dicho, las imágenes ocuparan todo el ancho y alto del slider. También tiene una posición absoluta para que se puedan desplaza hacia la derecha o izquierda fuera del slider, Por último, tiene un transition, para que el deslizamiento de derecha a izquierda o viceversa sea suave y con una duración de 0.6 segundos.

Las imágenes podrán estar a la izquierda o derecha, de acuerdo al botón que se presione:

Código: (css) [Seleccionar]
.slider .img.left {
  left: -100%;
}
.slider .img.right {
  right: -100%;
}

Por último, hay que darle estilizar los botones de control:

Código: (css) [Seleccionar]
.slider > .prevBtn, .slider > .nextBtn {
  background-color: transparent;
  border: none;
  color: rgba(250,250,250,.8);
  outline: none;
  position: absolute;
  top: calc(50% - 25px);
  z-index: 1;
}
.slider > .prevBtn {
  left: 10px;
}
.slider > .nextBtn {
  right: 10px;
}

Los botones no tendrán borde ni fondo (transparente). Tienen una posición absoluta lo que nos permitirá centrarla con la propiedad top, que tiene un cálculo de 50% - 25px que es el tamaño estimado de los botones. También, estarán a 10px de los lados izquierdos y derechos respectivamente.

JAVASCRIPT

Para éste propósito, y aprovechando las bondades de EcmaScript 6, vamos a crear una clase llamada Slider, que represente al objeto, como en la POO que todos conocemos.

Vayamos primero por el constructor:

Código: (javascript) [Seleccionar]
'use strict'
class Slider {
  constructor(el) {
    this.slider = el
    this.prevBtn = el.querySelector('.prevBtn')
    this.nextBtn = el.querySelector('.nextBtn')
    this.images = []
    this.current = 0
    this.url = this.slider.getAttribute('data-img-folder')
    this.init()
  }

El constructor recibe un parámetro, éste será el slider que hemos definido en el HTML. A partir de él, obtenemos los botones anterior y siguiente que controlarán el flujo del slider. También inicializamos la variable images como array y current con 0, que indicará la posición de la imagen actual dentro del array; también obtenemos la ruta del folder de las imágenes por medio del atributo especificado en el marcado HTML. Por último, llamamos al método init que vamos a describir a continuación:

Código: (javascript) [Seleccionar]
init() {
    this.prevBtn.addEventListener('click', function() {
      this.loadPrevImg()
    }.bind(this))
    this.nextBtn.addEventListener('click', function() {
      this.loadNextImg()
    }.bind(this))
    this.loadImages()
}

Lo único que hace el método init es escuchar por eventos click a cada botón y llamar a los métodos, loadPrevImg() y loadNextImg(). Por último, llamamos a loadImages(). Veamos que hace éste último método:

Código: (javascript) [Seleccionar]
loadImages() {
    let self = this;
    let imgUrls = [];
    let http = new XMLHttpRequest()
    http.open('GET', this.url, false)

    http.onload = function() {
      if(http.status === 200) {
        let html = document.createElement('html')
        html.innerHTML = this.response
        let links = html.querySelectorAll('a')
        for(let i=0; i<links.length; i++) {
          let content = (links[i].innerText).trim()
          if(self.isValidImg(content)) {
            imgUrls.push(self.url+content)
          }
        }
      } else {
        console.log('error: '+http.response)
      }
    }
    http.send()
    this.createImagesBlob(imgUrls)
}

Lo primero que hacemos es crear un objeto XMLHttpRequest para hacer una llamada AJAX. Abrimos la llamada con la ruta que especificamos en el atributo data-img-folder.

Si la petición ha sido exitosa, creamos todo un documento html, ésto es, porque la petición a un folder, trae un documento html con cada elemento en un link (lo hace el browser). Por eso 'parseamos' el texto devuelto en un elemento tipo html para poder obtener los links y la ruta de la imagen allí embebida.

Primero, obtenemos los links y los iteramos. Por cada link obtenemos el texto que tiene y le borramos los espacios con trim(). Luego, verificamos si contiene la extensión jpg, jpeg, png, o gif y si es cierto la añadimos al array de rutas de imágenes para luego precargarlas en blobs.

El método para determinar su el objeto actual es válido es isValidImg:

Código: (javascript) [Seleccionar]
isValidImg(file) {
    let suffixes = ['.jpg', '.jpeg', '.png', '.gif']
    for(var i=0; i<suffixes.length; i++) {
        if(endsWith(file, suffixes[i])) {
          return true
        }
    }
    function endsWith(str, suffix) {
      return str.indexOf(suffix, str.length - suffix.length) !== -1;
    }
    return false
}

Aquí simplemente itera un array de sufijos de imágenes y verifica si el elemento pasado finaliza con algunas de esas extensiones. Si es así, se trata de una imagen y devolvemos una bandera de luz verde.

Para crear blobs que representen a las imágenes, tenemos que hacer una llamada ajax por cada url de imagen y convertirla a blob. También es necesario, especificar que se devolverá un blob con la instrucción: http.responseType = 'blob':

Código: (javascript) [Seleccionar]
createImagesBlob(imgUrls) {
    let self = this;
    for(let i=0; i<imgUrls.length; i++) {
      let http = new XMLHttpRequest()
      http.responseType = 'blob'
      http.open('GET', imgUrls[i], true)
      http.onload = function() {
        if(this.status === 200) {
          self.images.push(window.URL.createObjectURL(this.response))
        }
      }
      http.send();
    } // end for
  }

Lo único que hacemos en el punto anterior es, hacer una llamada AJAX por cada ruta de imagen y especificar que se deve devolver un blob. Luego, creamos el blob con createObjectURL de la constante URL de window.

Sigamos con los métodos loadPrevImg y loadNextImg:

Código: (javascript) [Seleccionar]
loadPrevImg() {
    let self = this
    this.current = (this.current > 0) ? this.current - 1 : this.images.length - 1
    let img = this.createImage('left')
    this.slider.appendChild(img)

    setTimeout(function() {
      img.style.left = '0%'
      // 600 = .6s of slide animation
      setTimeout(function() {
          self.removeBefore(img)
      }, 600)
    },30)
  }
  loadNextImg() {
    let self = this
    this.current = (this.current < this.images.length - 1) ? this.current + 1 : 0
    let img = this.createImage('right')
    this.slider.appendChild(img)

    setTimeout(function() {
      img.style.right = '0%'
      // 600 = .6s of slide animation
      setTimeout(function() {
        self.removeBefore(img)
      }, 600)
    }, 30)
  }

Los métodos son muy parecidos (tarea, si quieres modulariza). En primera instancia, comprobamos si el índice está dentro del rango, si no, reseteamos (caso siguiente) o volvemos a la última imagen (caso anterior). Luego llamamos al método llamado createImage y le pasamos dos tipos de parámetros: left y right (ya lo veremos a continuación). Éste método crea un elemento IMG, le asigna la ruta de la imagen actual y retorna el elemento.

Una vez que ha retornado la imagen, le asignamos la propiedad left o right a 0%. Ésto lo que hará es mover la imagen de afuera hacia dentro del slider con la duración y transición que le hemos dado via CSS (¿recuerdas el transition: all .6s ease-in-out?). Por último, una vez que la imagen se ha mostrado, eliminamos la anterior creada. Importante, si no, agregarías la misma imagen una y otra vez y el HTML se haría enorme.

Veamos el método createImage:

Código: (javascript) [Seleccionar]
createImage(side) {
    let img = document.createElement('img')
    img.src = this.images[this.current]
    img.classList.add('img')
    if (side === 'right') {
      img.classList.add('right')
    } else {
      img.classList.add('left')
    }
    return img
  }

Simplemente creamos un elemento IMG y le asignamos la ruta de la imagen a mostrar. Aquí viene algo importante:

  • El parámetro es left: la imagen se deslizará de izquierda a derecha.
  • El parámetro es right: la imagen se deslizará de derecha a izquierda.


Ahora veamos el método removeBefore:

Código: (javascript) [Seleccionar]
removeBefore() {
    let child = this.slider.querySelector('img');
    this.slider.removeChild(child);
}

Este método es sencillo. Obtenemos el primer elemento tipo img del slider (que será el elemento img creado antes del nuevo) y lo removemos. De ésta manera siempre habrá solo un elemento img. Problemas de rendimiento, creo que ninguno, porque el manejo del DOM será de 2 cambios para 5 segundos.

Por último, una función para ponerlo en modo automático en caso quiera el usuario:

Código: (javascript) [Seleccionar]
setModeAuto() {
    setInterval(function() {
      this.loadNextImg()
    }.bind(this), 5000)
}




PD: Disculpen el delay al cargar las imágenes al principio. Lo que pasa es que Github no permite CORS y no pude obtener las imágenes como blob. En local, works like a charm.
« Última modificación: Junio 26, 2015, 11:09:54 pm por Gus Garsaky »

Desconectado rand0m

  • *
  • Underc0der
  • Mensajes: 214
  • Actividad:
    0%
  • Reputación 0
  • Paso de cosas personales, déjame
    • Ver Perfil
Me gusta mucho más que el Nivo Slider. Muchas gracias.
Podría vivir perfectamente con una mancha de pis en la alfombra, pero qué va, tío: Más complicaciones.