Display entity images with thumbnails and 1 large image
&lt;?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Field\FieldFormatter\SlickMyentityGalleryFormatter.
 */

namespace Drupal\mymodule\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Html;

/**
 * @FieldFormatter(
 *   id = "slick_myentity_gallery",
 *   label = @Translation("Slick Myentity Gallery"),
 *   field_types = {
 *     "image"
 *   }
 * )
 */
class SlickMyentityGalleryFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'visible_slides' =&gt; 3,
      'image_style' =&gt; 'thumbnail',
      'arrows' =&gt; TRUE,
      'dots' =&gt; TRUE,
      'fade' =&gt; FALSE,
      'standalone_image' =&gt; TRUE,
      'standalone_location' =&gt; 'above',
      'standalone_style' =&gt; 'xlarge',
      'vertical' =&gt; FALSE,
      'lazy_load' =&gt; 'ondemand',
      'hide_single_navigator' =&gt; TRUE
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $element = parent::settingsForm($form, $form_state);
    $field_name = $this-&gt;fieldDefinition-&gt;getName();

    $image_style_entities = \Drupal::entityManager()
      -&gt;getStorage('image_style')
      -&gt;loadMultiple();

    $image_styles = array_map(function($image_style) {
      return $image_style-&gt;label();
    }, $image_style_entities);

    $image_styles = ['_original' =&gt; '- ' . $this-&gt;t('Original') . ' -'] + $image_styles;

    $element['visible_slides'] = [
      '#type' =&gt; 'number',
      '#title' =&gt; $this-&gt;t('Number of visible slides'),
      '#default_value' =&gt; $this-&gt;getSetting('visible_slides'),
      '#min' =&gt; 1,
      '#max' =&gt; 10,
      '#description' =&gt; $this-&gt;t('Number of images visible at once.')
    ];

    $element['image_style'] = [
      '#type' =&gt; 'select',
      '#title' =&gt; $this-&gt;t('Image style'),
      '#default_value' =&gt; $this-&gt;getSetting('image_style'),
      '#options' =&gt; $image_styles,
      '#description' =&gt; $this-&gt;t('Image style to display the images with.')
    ];

    $element['arrows'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Show arrows'),
      '#default_value' =&gt; $this-&gt;getSetting('arrows'),
      '#description' =&gt; $this-&gt;t('Display the navigation arrows.')
    ];

    $element['dots'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Paging'),
      '#default_value' =&gt; $this-&gt;getSetting('dots'),
      '#description' =&gt; $this-&gt;t('Display the paging dots.')
    ];

    $element['fade'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Fade'),
      '#default_value' =&gt; $this-&gt;getSetting('fade'),
      '#description' =&gt; $this-&gt;t('Fade between transitions.')
    ];

    $element['vertical'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Vertical slider'),
      '#default_value' =&gt; $this-&gt;getSetting('vertical'),
      '#description' =&gt; $this-&gt;t('Display slider vertically instead of horizontally.')
    ];

    $element['lazy_load'] = [
      '#type' =&gt; 'select',
      '#title' =&gt; $this-&gt;t('Lazy load'),
      '#options' =&gt; [
        'none' =&gt; '- ' . $this-&gt;t('None') . ' -',
        'ondemand' =&gt; $this-&gt;t('On demand'),
        'progressive' =&gt; $this-&gt;t('Progressive')
      ],
      '#default_value' =&gt; $this-&gt;getSetting('lazy_load'),
      '#description' =&gt; $this-&gt;t('Lazy load the images to increase page performance.')
    ];

    $element['standalone_image'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Standalone active image'),
      '#default_value' =&gt; $this-&gt;getSetting('standalone_image'),
      '#description' =&gt; $this-&gt;t('Display the active image separately.')
    ];

    $element['standalone_style'] = [
      '#type' =&gt; 'select',
      '#title' =&gt; $this-&gt;t('Standalone image style'),
      '#default_value' =&gt; $this-&gt;getSetting('standalone_style'),
      '#options' =&gt; $image_styles,
      '#description' =&gt; $this-&gt;t('Image style to display the standalone image with.'),
      '#states' =&gt; [
        'visible' =&gt; [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" =&gt; [
            'checked' =&gt; TRUE
          ]
        ]
      ]
    ];

    $element['standalone_location'] = [
      '#type' =&gt; 'select',
      '#title' =&gt; $this-&gt;t('Standalone image location'),
      '#default_value' =&gt; $this-&gt;getSetting('standalone_location'),
      '#options' =&gt; [
        'above' =&gt; $this-&gt;t('Above'),
        'below' =&gt; $this-&gt;t('Below'),
        'left' =&gt; $this-&gt;t('Left'),
        'right' =&gt; $this-&gt;t('Right')
      ],
      '#description' =&gt; $this-&gt;t('Display slider vertically instead of horizontally.'),
      '#states' =&gt; [
        'visible' =&gt; [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" =&gt; [
            'checked' =&gt; TRUE
          ]
        ]
      ]
    ];

    $element['hide_single_navigator'] = [
      '#type' =&gt; 'checkbox',
      '#title' =&gt; $this-&gt;t('Hide single item carousel'),
      '#default_value' =&gt; $this-&gt;getSetting('hide_single_navigator'),
      '#description' =&gt; $this-&gt;t('If standalone image is displayed and there is only one image, the carousel will be hidden.'),
      '#states' =&gt; [
        'visible' =&gt; [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" =&gt; [
            'checked' =&gt; TRUE
          ]
        ]
      ]
    ];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = parent::settingsSummary();

    if ($this-&gt;getSetting('standalone_image')) {
      $standalone_style = $this-&gt;getSetting('standalone_style');
      if ($standalone_style == '_original') {
        $summary[] = $this-&gt;t('Display standalone image without image style.');
      } else {
        $style = \Drupal::entityManager()-&gt;getStorage('image_style')-&gt;load($standalone_style);
        $summary[] = $this-&gt;t('Display standalone image with %style image style.', [
          '%style' =&gt; $style ? $style-&gt;label() : 'original'
        ]);
      }

      $location = $this-&gt;getSetting('standalone_location');
      $location_labels = [
        'above' =&gt; $this-&gt;t('above'),
        'below' =&gt; $this-&gt;t('below'),
        'left' =&gt; $this-&gt;t('left'),
        'right' =&gt; $this-&gt;t('right')
      ];
      $summary[] = $this-&gt;t('Standalone image position: %position.', [
        '%position' =&gt; $location_labels[$location]
      ]);

      if ($this-&gt;getSetting('hide_single_navigator')) {
        $summary[] = $this-&gt;t('Hide carousel with single image.');
      }
    }

    $image_style = $this-&gt;getSetting('image_style');
    if ($image_style == '_original') {
      $summary[] = $this-&gt;t('Display images without image style.');
    } else {
      $style = \Drupal::entityManager()-&gt;getStorage('image_style')-&gt;load($image_style);
      $summary[] = $this-&gt;t('Display images with %style image style.', [
        '%style' =&gt; $style ? $style-&gt;label() : 'original'
      ]);
    }

    if ($this-&gt;getSetting('vertical')) {
      $summary[] = $this-&gt;t('Display slider vertically.');
    }

    switch ($this-&gt;getSetting('lazy_load')) {
      case 'ondemand':  $summary[] = $this-&gt;t('Use &lt;em&gt;on demand&lt;/em&gt; lazy loading.'); break;
      case 'progressive':  $summary[] = $this-&gt;t('Use &lt;em&gt;progressive&lt;/em&gt; lazy loading.'); break;
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    // Skip if there are no items to display.
    if ($items-&gt;isEmpty()) {
      return [];
    }

    $standalone_html_id = Html::getUniqueId($items-&gt;getName() . '-standalone');
    $carousel_id = Html::getUniqueId($items-&gt;getName() . '-nav');
    $lazy_load = $this-&gt;getSetting('lazy_load');
    $use_lazy_load = $lazy_load != 'none';

    // Retrieve file IDs.
    $fids = [];
    foreach ($items-&gt;getIterator() AS $item) {
      $fids[] = $item-&gt;get('target_id')-&gt;getValue();
    }

    // Load files so we can get their URIs.
    $files = \Drupal::entityManager()-&gt;getStorage('file')-&gt;loadMultiple($fids);

    // Make a list of all URIs.
    $uris = array_map(function($file) {
      return $file-&gt;getFileUri();
    }, $files);

    // Set up the render element.
    $element = [
      '#attached' =&gt; [
        'library' =&gt; [
          'mymodule/slick_myentity_gallery'
        ]
      ]
    ];

    // Create carousel for the standalone image if needed.
    if ($this-&gt;getSetting('standalone_image')) {
      $element['#attributes']['class'][] = 'with-standalone';

      $element['standalone'] = [
        '#theme' =&gt; 'item_list',
        '#items' =&gt; [],
        '#weight' =&gt; 0,
        '#attributes' =&gt; [
          'id' =&gt; $standalone_html_id,
          'class' =&gt; [
            'slick-myentity-gallery',
            'slider-standalone',
            'location-' . $this-&gt;getSetting('standalone_location')
          ],
          'data-slick-settings' =&gt; json_encode([
            'slidesToShow' =&gt; 1,
            'slidesToScroll' =&gt; 1,
            'arrows' =&gt; FALSE,
            'fade' =&gt;  TRUE,
            'asNavFor' =&gt; '#' . $carousel_id,
            'lazyLoad' =&gt; $lazy_load,
            'mobileFirst' =&gt; TRUE
          ])
        ]
      ];

      foreach ($uris AS $uri) {
        $element['standalone']['#items'][] = $this-&gt;getImageItem($uri, $this-&gt;getSetting('standalone_style'), $use_lazy_load);
      }
    }

    // Create the main image carousel.
    $element['carousel'] = [
      '#theme' =&gt; 'item_list',
      '#items' =&gt; [],
      '#weight' =&gt; 1,
      '#attributes' =&gt; [
        'id' =&gt; $carousel_id,
        'class' =&gt; [
          'slick-myentity-gallery',
          'slider-navigation',
          $this-&gt;getSetting('vertical') ? 'vertical' : 'horizontal'
        ],
        'data-slick-settings' =&gt; json_encode([
          'slidesToShow' =&gt; (int) $this-&gt;getSetting('visible_slides'),
          'slidesToScroll' =&gt; 1,
          'asNavFor' =&gt; '#' . $standalone_html_id,
          'dots' =&gt; (bool) $this-&gt;getSetting('dots'),
          'arrows' =&gt; (bool) $this-&gt;getSetting('arrows'),
          'fade' =&gt; (bool) $this-&gt;getSetting('fade'),
          'vertical' =&gt; (bool) $this-&gt;getSetting('vertical'),
          'verticalSwiping' =&gt; (bool) $this-&gt;getSetting('vertical'),
          'focusOnSelect' =&gt; TRUE,
          'lazyLoad' =&gt; $lazy_load,
          'mobileFirst' =&gt; TRUE,
          'rtl' =&gt; \Drupal::languageManager()-&gt;getCurrentLanguage()-&gt;getDirection() == 'rtl',
          'infinite' =&gt; FALSE
        ])
      ]
    ];

    foreach ($uris AS $uri) {
      $element['carousel']['#items'][] = $this-&gt;getImageItem($uri, $this-&gt;getSetting('image_style'), $use_lazy_load);
    }

    // Display the carouse first if the standalone image is visible and
    // is positioned below the carousel or to the right next to it.
    if ($this-&gt;getSetting('standalone_image') &amp;&amp; in_array($this-&gt;getSetting('standalone_location'), ['below', 'right'])) {
      $element['carousel']['#weight'] = -1;
    }

    // Hide navigator if standalone image is displayed and there is only
    // one image.
    if ($this-&gt;getSetting('standalone_image') &amp;&amp; $this-&gt;getSetting('hide_single_navigator') &amp;&amp; $items-&gt;count() == 1) {
      unset($element['carousel']);
    }

    return [$element];
  }

  /**
   * Helper method to generate image for provided style
   * that takes the lazy load setting into account.
   *
   * @param string $uri
   *   The original image uri.
   * @param string $image_style
   *   The image style name.
   * @param bool $lazy_load
   *   If TRUE the lazy slick load image will be returned.
   *
   * @return array
   *   Renderable array for a single image.
   */
  function getImageItem($uri, $image_style, $lazy_load = FALSE) {
    if ($lazy_load) {
      $style = \Drupal::entityManager()
        -&gt;getStorage('image_style')
        -&gt;load($image_style);

      return [
        '#type' =&gt; 'inline_template',
        '#template' =&gt; '&lt;img data-lazy="{{ path }}" /&gt;',
        '#context' =&gt; [
          'path' =&gt; $style ? $style-&gt;buildUrl($uri) : file_create_url($uri)
        ]
      ];
    } else {
      return [
        '#theme' =&gt; 'image_style',
        '#uri' =&gt; $uri,
        '#style_name' =&gt; $image_style
      ];
    }
  }

}


(function($) {

  Drupal.behaviors.slickMyentityGallery = {
    attach: function(context) {
      // Initialize the Slick Carousel on all appropriate elements.
      $('.slick-myentity-gallery[data-slick-settings]', context).once('slick-myentity-gallery-init').each(function() {
        var $this = $(this);
        var settings = $this.data('slick-settings');

        // Adjust behavior if this is a Field API field.
        var $fapi_items = $this.find('.field__item');
        if ($fapi_items.length != 0) {
          $fapi_items.parent().slick( settings );
        } else {
          $this.slick( settings );
        }
      });
    }
  }

})(jQuery);