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

Iniciado por Gus Garsaky, Junio 26, 2015, 06:21:00 PM

Tema anterior - Siguiente tema

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

Siguiendo con estos pequeños artículos de front end development, hoy vamos a realizar un slider utilizando JavaScript y un poco de CSS.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
(Click en la imagen para ver el demo)

CitarTodo el código fuente está en Github: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
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

    <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

    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

    .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

    .slider .img.left {
      left: -100%;
    }
    .slider .img.right {
      right: -100%;
    }


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

    Código: css

    .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

    '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

    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

    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

    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

    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

    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

    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

    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

    setModeAuto() {
        setInterval(function() {
          this.loadNextImg()
        }.bind(this), 5000)
    }





    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

    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.

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.