[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.

You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login
(Click en la imagen para ver el demo)

CitarTodo el código fuente está en Github: You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login
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)
    }





    You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login

    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.