Tutorial sobre Masonry

06/01/2015
Tutorial sobre Masonry

Aprovechando que hemos aplicado en La Drupalera la librería Masonry para la distribución de los artículos, aquí va un pequeño tutorial para que podáis implementar esta funcionalidad en vuestro tema.

¿Qué es Masonry?

Para aquellos usuarios que desconozcan qué es Masonry y cómo funciona, se trata de una librería javascript que nos permite posicionar de manera óptima una serie de elementos en función del espacio vertical disponible.

Para entender con mayor claridad el efecto visual que podemos llegar a obtener, echémosle un vistazo a la página oficial (cortesía de “http://v3.desandro.com”) donde encontraremos toda la documentación necesaria para su instalación, así como páginas que pueden servirnos como ejemplo.

Masonry methods

Las opciones que nos brinda Masonry son numerosas. No solo se trata de posicionar elementos en un espacio, sino que además podemos elegir de qué forma y a qué eventos reaccionan dichos elementos. En el apartado Methods encontraremos todos los comportamientos que podemos atribuir. El posicionamiento de manera aleatoria al hacer click en un botón (Appended method), la ampliación de nuestros elementos al hacer click sobre ellos y su correspondiente adaptación al nuevo espacio disponible (Layout method), además de poder eliminar uno de esos elementos y reubicar de nuevo el resto (Remove) son algunos de los comportamientos que podemos realizar con el uso de Masonry.

Este último método es el que hemos utilizado para La Drupalera y el que voy a explicar en este tutorial.

Creando nuestra vista

Como primer paso crearemos la vista que albergará los elementos a posicionar. En nuestro caso es una vista muy simple que muestra los contenidos de tipo artículo de nuestro portal. 

1-5.png

Hemos maquetado cada artículo con una anchura del 33% con el objetivo de visualizar tres artículos por fila.

Algo importante a tener en cuenta es el no haber especificado la flotación de los artículos. El motivo es que Masonry posiciona de forma absoluta cada elemento, tan solo necesita tener constancia del ancho de los artículos para distribuirlos por el espaciado disponible. 

Por ahora nuestra vista tendrá esta apariencia e irá evolucionando a lo largo del tutorial.

33.png

Codigo Sass:

.page-masonry
  .view-masonry
    .view-content
      overflow: hidden
      .views-row
        margin: 0
        width: 33%
        cursor: pointer

Instalación

Para instalar Masonry solo necesitamos el siguiente plugin:

Copiaremos en el directorio “../theme/js/plugins/” dicho archivo con el código que encontraremos en su interior. Para nuestra comodidad lo hemos renombrado a “masonry.js”.

Atacando al preprocess view

Ahora nuestro objetivo es que el plugin Masonry solo se cargue cuando sea necesario, es decir, solo y exclusivamente en la vista que acabamos de crear. Para ello en "template.preprocess.view.php" escribimos la siguiente función:

template.preprocess.view.php:

<?php

function hook_preprocess_views_view(&$vars) {

  if ($vars['view']->name == 'masonry'){
       $theme_path = drupal_get_path('theme', 'hook');
       drupal_add_js($theme_path . '/js/plugins/masonry.js');
  }
}

?>

Tenemos que asegurarnos de que el nombre de la vista definida en dicha condición coincida con el nombre que le pusimos al crearla.

Remove method

Como dijimos antes, La Drupalera tiene implementado el método "remove" así que vamos a aprovechar para explicar su funcionamiento y replicarlo en otro portal.

En el siguiente enlace tenemos el código y un ejemplo del funcionamiento.

3.png

Tenemos la posibilidad de ver el ejemplo en Codepen, el cual personalmente prefiero, ya que nos ofrece el código HTML, CSS y JS: 

codepen.png

Tal y como vemos en la imagen lo que nos interesa es el código JS. Creamos en el directorio “../theme/js/” un fichero llamado “masonry-remove.js” donde pegaremos el código anterior. Lo añadimos al preprocess de la vista, para que al igual que el plugin básico, solo cargue cuando nos encontremos en la vista elegida.

template.preprocess.view.php:

<?php

function hook_preprocess_views_view(&$vars) {

  if ($vars['view']->name == 'masonry'){
    $theme_path = drupal_get_path('theme', 'hook');
    drupal_add_js($theme_path . '/js/plugins/masonry.js');
    drupal_add_js($theme_path . '/js/masonry-remove.js');
  }
}

?>

Masonry-remove.js

Nos centramos ahora en el último fichero creado; vemos que comienza con una serie de líneas comentadas donde se nos especifica los plugins que necesita este método:  

  • masonry.pkgd.js (nuestro “masonry.js”)

  • classie.js

Classie es un plugin javascript que nos permite añadir, eliminar, alterar y comprobar clases del DOM. Si basamos nuestro código en jQuery no será necesario tener este plugin, pero eso lo comentaré más adelante. 

Seguimos el mismo procedimiento anterior, creamos un fichero en “../theme/js/plugins” llamado classie.js y lo añadimos al preprocess de la vista.

template.preprocess.view.php:

<?php

function hook_preprocess_views_view(&$vars) {

  if ($vars['view']->name == 'masonry'){

    $theme_path = drupal_get_path('theme', 'hook');
      drupal_add_js($theme_path . '/js/plugins/masonry.js');
      drupal_add_js($theme_path . '/js/plugins/classie.js');
      drupal_add_js($theme_path . '/js/masonry-remove.js');
  }
}

?>

Volvemos al código para desmenuzar cada función.

masonry-remove.js:

(function ($) {
  Drupal.behaviors.da_vinciThemeMasonry = {
    attach: function (context) {
      var container = document.querySelector('.view-masonry');
      var msnry = new Masonry(container, {
        itemSelector: '.views-row',
        columnWidth: '.views-row'
      });

      // Close event and remove element
      eventie.bind(container, 'click', function (event) {
        if (!classie.has(event.target, 'close')) {
        return;
        }
        msnry.remove($(event.target).closest('li'));
        msnry.layout();
      });
    }
  }
})(jQuery);

Realmente es un código muy sencillo; se trata de capturar en una variable aquella capa que contiene los elementos a posicionar (siendo esta la clase de nuestra vista), especificando la clase de dichos elementos. Si creamos la vista en formato HTML List, corresponderá con la clase del <li>.

colorines.png

Nosotros hemos añadido un parámetro más. Se trata de la anchura de cada <li>  “columnwidth”, ya que basamos nuestra maquetación en una anchura del 33% para cada artículo.

Cada <li> va a reaccionar a un evento click que por defecto viene atribuido a un elemento de clase “close”. Ya que no disponemos en nuestro artículo de ningún elemento con dicha clase, una solución muy sencilla es añadirlo mediante js para que actúe como botón de cierre. 

// Close element
$('.view-masonry .node-article').append('<span class="close">close</span>');

 masonry-remove.js:

(function ($) {
  Drupal.behaviors.da_vinciThemeMasonry = {
    attach: function (context) {
      var container = document.querySelector('.view-masonry');
      var msnry = new Masonry(container, {
        itemSelector: '.views-row',
        columnWidth: '.views-row'
      });
      
      // Close event and remove element
      eventie.bind(container, 'click', function (event) {
        if (!classie.has(event.target, 'close')) {
        return;
        }
        msnry.remove($(event.target).closest('li'));
        msnry.layout();
      });

      // Close element
      $('.view-masonry .node-article').append('<span class="close">close</a>');
    }
  }
})(jQuery);

equis.png

Cada vez que cerremos un artículo, Masonry calculará el nuevo espacio disponible acoplando el resto de elementos a ese espacio. Si no hemos tenido ningún fallo de javascript deberíamos poder ver el funcionamiento del método "remove" en nuestra vista.

Masonry + Infinite scroll + Imageload

Para explotar al máximo esta librería, vamos a aplicarle un scroll infinito a nuestra vista para que los artículos se vayan cargando conforme hagamos scroll, siguiendo estos pasos.

Nos descargamos el módulo mediante drush:

drush dl views_infinite_scroll

Lo activamos:

drush en -y views_infinite_scroll

Descargamos “autopager”, un parche necesario para el correcto funcionamiento del módulo:

drush dl-autopager

Ahora al dirigirnos a la configuración de nuestra vista tendremos como opción disponible el scroll infinito:

infinitescrolloption.png

Al comprobar el funcionamiento del scroll infinito vemos que conforme hacemos scroll se van mostrando más artículos, sin embargo, aparecen solapados entre ellos.

solapamiento.png

¿Por qué ocurre esto?

Esto ocurre porque el cálculo de la altura de cada <li>, así como el proceso de disposición es mucho más rápido que la carga de imágenes. Con el plugin "imagesloaded" conseguimos anticiparnos a ese posicionamiento cargando todas las imágenes para tener constancia del tamaño real de cada contenido y evitando así que los elementos se solapen. 

Creamos en "../theme/js/plugins" un fichero donde incluir el código anterior y posteriormente lo añadimos al preprocess de la vista:

template.preprocess.view.php:

<?php
function hook_preprocess_views_view(&$vars) {

  if ($vars['view']->name == 'masonry'){

    $theme_path = drupal_get_path('theme', 'hook');
      drupal_add_js($theme_path . '/js/plugins/masonry.js');
      drupal_add_js($theme_path . '/js/plugins/classie.js');
      drupal_add_js($theme_path . '/js/plugins/imageload.js');
      drupal_add_js($theme_path . '/js/masonry-remove.js');
  }
}
?>

Una vez añadida la ruta volvemos a "masonry-remove.js" e insertamos el siguiente código:

imagesLoaded(container, function () {
      msnry.layout();
});

Nuestro archivo "masonry-remove.js" debería presentarse de la siguiente manera:

masonry-remove.js 

(function ($) {
  Drupal.behaviors.da_vinciThemeMasonry = {
    attach: function (context) {
      var container = document.querySelector('.view-masonry');
      var msnry = new Masonry(container, {
        itemSelector: '.views-row',
        columnWidth: '.views-row'
      });

      // ImageLoaded
      imagesLoaded(container, function () {
        msnry.layout();
      });

      // Close event and remove element
      eventie.bind(container, 'click', function (event) {
        if (!classie.has(event.target, 'close')) {
        return;
        }
        msnry.remove($(event.target).closest('li'));
        msnry.layout();
      });

      // Close element
      $('.view-masonry .node-article').append('<span class="close">close</a>');
    }
  }
})(jQuery);

Transformando nuestro código a jQuery

Hasta ahora, el código ofrecido desde http://masonry.desandro.com y sobre el que hemos trabajado viene dado en javascript; sin embargo, también se nos brinda la posibilidad de pasar dicho código a jQuery

Código masonry-remove.js en jQuery:

(function ($) {
  Drupal.behaviors.da_vinciThemeMasonry = {
    attach: function (context) {
      var $container = $('.view-masonry').masonry({
        itemSelector: '.views-row',
        columnWidth: '.views-row'
      }).imagesLoaded( function() {
        $container.masonry();
      });

      // Close event and element remove
      $container.find('.views-row .close').click (function(){
      $(this).parent('.views-row').remove();
      $container.masonry();

      // Close element
      $('.view-masonry .node-article').append('<span class="close">close</a>');
      });

    }
  }
})(jQuery);

El único problema es que no se comporta de manera óptima al implementar el scroll infinito, por lo que personalmente he mantenido el código javascript a esperas de encontrar una solución.

Como habéis podido ver, es muy sencillo aplicar esta magnífica librería. ¡No hay excusa para no innovar y no aportar un toque diferente a nuestro portal!

ENJOY!