Europa Component Library

Carousels

Carousels show a collection of items one at a time. They are also known as “slideshows” and “sliders”. Typical uses of carousels include scrolling news headlines, featured articles on home pages, and image galleries.

Resources

{#

Usage:

  - css_class: (string)
    Possible set of classes in addition to `ecl-carousel` wrapper.

  - extra_attributes: (iterable)
    Conventional map of possible extra attributes for the wrapper.
    Pass any attribute, except: id, aria-labelledby

  - carousel_id: (string)
    The id of the carousel.
    Defaults to `ecl-carousel`.

  - carousel_aria_labelledby: (string)
    The value for `aria-labelledby` attribute for of heeading of the carousel.
    Defaults to `ecl-carousel__heading`.
    This value MUST be the same as `heading_id`.

  - heading_attributes: (iterable)
    Conventional map of possible extra attributes for the header.
    Pass any attribute, except: id, class.

  - heading_id: (string)
    The id of carousel heading.
    Defaults to `ecl-carousel__heading`.
    This value MUST be the same as `carousel_aria_labelledby`.

  - heading_title: (string)
    Carousel heading value.
    Defaults to `gallery`.

  - carousel_images (iterable)
    A list of images following the structure:

    {
      image: {
        src: '<path_to_image>',
        alt: 'Image item alt text',
      },
      download: {
        target: '#',
        title: 'Download',
        label: 'Download',
      },
      share: {
        target: '#',
        title: 'Share',
        label: 'Share',
      },
      description: (string or block).
      copyright: '© Copyright 1',
    }

#}

{# Internal properties #}

{% set _css_class = 'ecl-carousel' %}
{% set _extra_attributes = '' %}

{% set _carousel_id = carousel_id|default('ecl-carousel') %}
{% set _carousel_aria_labellby = carousel_aria_labellby|default('ecl-carousel__heading') %}
{% set _heading_id = heading_id|default('ecl-carousel__heading') %}
{% set _heading_title = heading_title|default('gallery') %}

{# Internal logic - Process properties #}

{# Carousel classes #}
{% if css_class is defined %}
  {% set _css_class = _css_class ~ ' ' ~ css_class %}
{% endif %}

{# Carousel attributes, except id and aria-labelledby #}
{% if extra_attributes is defined %}
  {% for attr in extra_attributes %}
    {% if attr != 'id' and attr != 'aria-labelledby' %}
      {% set _extra_attributes = _extra_attributes ~ ' ' ~ attr.name ~ '="' ~ attr.value ~ '"' %}
    {% endif %}
  {% endfor %}
{% endif %}

{# Carousel id and aria-labelledby connecting to heading #}
{% set _extra_attributes = _extra_attributes ~ ' ' ~ 'id="' ~ _carousel_id ~ '"' %}
{% set _extra_attributes = _extra_attributes ~ ' ' ~ 'aria-labelledby="' ~ _carousel_aria_labellby ~ '"' %}

{# Heading attributes except the id #}
{% if heading_attributes is defined %}
  {% for heading_attr in heading_attributes %}
    {% if heading_attr != 'id' %}
      {% set _heading_attributes = _heading_attributes ~ ' ' ~ heading_attr.name ~ '="' ~ heading_attr.value ~ '"' %}
    {% endif %}
  {% endfor %}
{% endif %}

{# Heading id #}
{% set _heading_attributes = _heading_attributes ~ 'id="' ~ _heading_id ~ '"' %}

{# Print result #}
<section class="{{ _css_class }}" {{ _extra_attributes|raw }}>
  <h3 class="ecl-headings ecl-headings--h3 ecl-u-sr-only" {{ _heading_attributes }} >{{ _heading_title }}</h3>
  <div class="ecl-carousel__list-wrapper">
    <ul class="ecl-carousel__list ecl-list--unstyled">
      {% for carousel_image in carousel_images %}
      <li class="ecl-carousel__item">
        <img src="{{ carousel_image.image.src }}" alt="{{ carousel_image.image.alt }}" class="ecl-carousel__image" />
      </li>
      {% endfor %}
    </ul>
  </div>
  <div class="ecl-carousel__live-region">
    {% for carousel_image in carousel_images %}
    <div class="ecl-carousel__image-information" data-image={{ loop.index0 }}>
      <div class="ecl-carousel__meta">
        <span class="ecl-carousel__meta-item ecl-carousel__meta-download">
          {% include '@ec-europa/ecl-links' with {
            'extra_classes': 'ecl-carousel__meta-link ecl-icon ecl-icon--download',
            'extra_attributes': [ 'name': 'title', 'value': carousel_image.download.title ],
            'variant': ['inverted', 'standalone'],
            }|merge(carousel_image.download)
          %}
        </span>
        <span class="ecl-carousel__meta-item ecl-carousel__meta-share">
          {% include '@ec-europa/ecl-links' with {
            'extra_classes': 'ecl-carousel__meta-link ecl-icon ecl-icon--share',
            'extra_attributes': [ 'name': 'title', 'value': carousel_image.share.title ],
            'variant': ['inverted', 'standalone'],
            }|merge(carousel_image.share)
          %}
        </span>
      </div>
      <div class="ecl-carousel__image-description">
        {% block description %}
          {{ carousel_image.description }}
        {% endblock %}
      </div>
      <div class="ecl-carousel__image-copyright">
        {{ carousel_image.copyright }}
      </div>
    </div>
    {% endfor %}
  </div>
</section>
{
  "_demo": {
    "scripts": "\n        document.addEventListener('DOMContentLoaded', function () {\n            ECL.carousels();\n        });\n      "
  },
  "carousel_images": [
    {
      "image": {
        "src": "../../assets/demo_images/business-demo-1.jpg",
        "alt": "First item"
      },
      "download": {
        "href": "#",
        "title": "Download",
        "label": "Download"
      },
      "share": {
        "href": "#",
        "title": "Share",
        "label": "Share"
      },
      "description": "<p>Nulla consequat massa quis enim. Donec pede justo.</p>\n          <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href=\"#\" title=\"Cras dapibus\">Cras dapibus</a>. Vivamus elementum semper nisi.</p>",
      "copyright": "© Copyright 1"
    },
    {
      "image": {
        "src": "../../assets/demo_images/business-demo-2.jpg",
        "alt": "Second item"
      },
      "download": {
        "href": "#",
        "title": "Download",
        "label": "Download"
      },
      "share": {
        "href": "#",
        "title": "Share",
        "label": "Share"
      },
      "description": "<p>Nulla consequat massa quis enim. Donec pede justo.</p>\n          <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href=\"#\" title=\"Cras dapibus\">Cras dapibus</a>. Vivamus elementum semper nisi.</p>",
      "copyright": "© Copyright 2"
    },
    {
      "image": {
        "src": "../../assets/demo_images/business-demo-3.jpg",
        "alt": "Third item"
      },
      "download": {
        "href": "#",
        "title": "Download",
        "label": "Download",
        "variant": [
          "inverted",
          "standalone"
        ]
      },
      "share": {
        "href": "#",
        "title": "Share",
        "label": "Share",
        "variant": [
          "inverted",
          "standalone"
        ]
      },
      "description": "<p>Nulla consequat massa quis enim. Donec pede justo.</p>\n          <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href=\"#\" title=\"Cras dapibus\">Cras dapibus</a>. Vivamus elementum semper nisi.</p>",
      "copyright": "© Copyright 3"
    }
  ]
}
<section class="ecl-carousel" id="ecl-carousel" aria-labelledby="ecl-carousel__heading">
  <h3 class="ecl-headings ecl-headings--h3 ecl-u-sr-only" id="ecl-carousel__heading">gallery</h3>
  <div class="ecl-carousel__list-wrapper">
    <ul class="ecl-carousel__list ecl-list--unstyled">
      <li class="ecl-carousel__item">
        <img src="../../assets/demo_images/business-demo-1.jpg" alt="First item" class="ecl-carousel__image" />
      </li>
      <li class="ecl-carousel__item">
        <img src="../../assets/demo_images/business-demo-2.jpg" alt="Second item" class="ecl-carousel__image" />
      </li>
      <li class="ecl-carousel__item">
        <img src="../../assets/demo_images/business-demo-3.jpg" alt="Third item" class="ecl-carousel__image" />
      </li>
    </ul>
  </div>
  <div class="ecl-carousel__live-region">
    <div class="ecl-carousel__image-information" data-image=0>
      <div class="ecl-carousel__meta">
        <span class="ecl-carousel__meta-item ecl-carousel__meta-download">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--download" href="#" =":" ="" =":" ="">Download</a>
        </span>
        <span class="ecl-carousel__meta-item ecl-carousel__meta-share">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--share" href="#" =":" ="" =":" ="">Share</a>
        </span>
      </div>
      <div class="ecl-carousel__image-description">
        <p>Nulla consequat massa quis enim. Donec pede justo.</p>
        <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href="#" title="Cras dapibus">Cras dapibus</a>. Vivamus elementum semper nisi.</p>
      </div>
      <div class="ecl-carousel__image-copyright">
        © Copyright 1
      </div>
    </div>
    <div class="ecl-carousel__image-information" data-image=1>
      <div class="ecl-carousel__meta">
        <span class="ecl-carousel__meta-item ecl-carousel__meta-download">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--download" href="#" =":" ="" =":" ="">Download</a>
        </span>
        <span class="ecl-carousel__meta-item ecl-carousel__meta-share">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--share" href="#" =":" ="" =":" ="">Share</a>
        </span>
      </div>
      <div class="ecl-carousel__image-description">
        <p>Nulla consequat massa quis enim. Donec pede justo.</p>
        <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href="#" title="Cras dapibus">Cras dapibus</a>. Vivamus elementum semper nisi.</p>
      </div>
      <div class="ecl-carousel__image-copyright">
        © Copyright 2
      </div>
    </div>
    <div class="ecl-carousel__image-information" data-image=2>
      <div class="ecl-carousel__meta">
        <span class="ecl-carousel__meta-item ecl-carousel__meta-download">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--download" href="#" =":" ="" =":" ="">Download</a>
        </span>
        <span class="ecl-carousel__meta-item ecl-carousel__meta-share">
          






                            
  
                          

<a class="ecl-link ecl-link--inverted ecl-link--standalone ecl-carousel__meta-link ecl-icon ecl-icon--share" href="#" =":" ="" =":" ="">Share</a>
        </span>
      </div>
      <div class="ecl-carousel__image-description">
        <p>Nulla consequat massa quis enim. Donec pede justo.</p>
        <p>In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Integer tincidunt. <a href="#" title="Cras dapibus">Cras dapibus</a>. Vivamus elementum semper nisi.</p>
      </div>
      <div class="ecl-carousel__image-copyright">
        © Copyright 3
      </div>
    </div>
  </div>
</section>
  • Content:
    import { queryAll } from '@ec-europa/ecl-base/helpers/dom';
    import debounce from 'lodash.debounce';
    
    /**
     * @param {object} options Object containing configuration overrides
     */
    export const carousels = ({ selectorId: selectorId = 'ecl-carousel' } = {}) => {
      // SUPPORTS
      if (!('querySelector' in document) || !('addEventListener' in window)) {
        return null;
      }
    
      // SETUP
      let currentSlide = 0;
      const carousel = document.getElementById(selectorId);
      const slides = queryAll('.ecl-carousel__item', carousel);
      const list = carousel.querySelector('.ecl-carousel__list');
    
      function getListItemWidth() {
        return carousel.querySelector('.ecl-carousel__item').offsetWidth;
      }
    
      function goToSlide(n) {
        slides[currentSlide].classList.remove('ecl-carousel__item--showing');
        currentSlide = (n + slides.length) % slides.length;
        slides[currentSlide].classList.add('ecl-carousel__item--showing');
      }
    
      function setOffset() {
        const itemWidth = getListItemWidth();
        const tr = `translate3d(${-currentSlide * itemWidth}px, 0, 0)`;
    
        list.style.MozTransform = tr; /* FF */
        list.style.msTransform = tr; /* IE (9+) */
        list.style.OTransform = tr; /* Opera */
        list.style.WebkitTransform = tr; /* Safari + Chrome */
        list.style.transform = tr;
      }
    
      function announceCurrentSlide() {
        carousel.querySelector(
          '.ecl-carousel__meta-slide'
        ).textContent = `${currentSlide + 1} / ${slides.length}`;
      }
    
      function showImageInformation() {
        // Reset/Hide all.
        const infoAreas = queryAll('[data-image]');
        // If anything is visible.
        if (infoAreas) {
          // eslint-disable-next-line
          infoAreas.forEach(area => (area.style.display = 'none'));
        }
    
        carousel.querySelector(`[data-image="${currentSlide}"]`).style.display =
          'block';
      }
    
      function previousSlide() {
        goToSlide(currentSlide - 1);
        setOffset();
        announceCurrentSlide();
        showImageInformation();
      }
    
      function nextSlide() {
        goToSlide(currentSlide + 1);
        setOffset();
        announceCurrentSlide();
        showImageInformation();
      }
    
      // Attach controls to a carousel.
      function addCarouselControls() {
        const navControls = document.createElement('ul');
    
        navControls.className = 'ecl-carousel__controls ecl-list--unstyled';
    
        navControls.innerHTML = `
          <li>
            <button type="button" class="ecl-icon ecl-icon--left ecl-carousel__button ecl-carousel__button--previous">
              <span class="ecl-u-sr-only">Previous</span></button>
          </li>
          <li>
            <button type="button" class="ecl-icon ecl-icon--right ecl-carousel__button ecl-carousel__button--next">
              <span class="ecl-u-sr-only">Next</span>
            </button>
          </li>
        `;
    
        navControls
          .querySelector(
            '.ecl-carousel__button--previous',
            '.ecl-carousel__controls'
          )
          .addEventListener('click', previousSlide);
    
        navControls
          .querySelector('.ecl-carousel__button--next', '.ecl-carousel__controls')
          .addEventListener('click', nextSlide);
    
        carousel
          .querySelector('.ecl-carousel__list-wrapper')
          .appendChild(navControls);
      }
    
      function removeCarouselControls() {
        const controls = carousel.querySelector('.ecl-carousel__controls');
        carousel.querySelector('.ecl-carousel__list-wrapper').removeChild(controls);
      }
    
      function addLiveRegion() {
        const liveRegion = document.createElement('div');
        liveRegion.setAttribute('aria-live', 'polite');
        liveRegion.setAttribute('aria-atomic', 'true');
        liveRegion.classList.add('ecl-carousel__meta-slide');
        carousel
          .querySelector('.ecl-carousel__live-region')
          .appendChild(liveRegion);
      }
    
      function removeLiveRegion() {
        const liveRegion = carousel.querySelector('.ecl-carousel__meta-slide');
        carousel
          .querySelector('.ecl-carousel__live-region')
          .removeChild(liveRegion);
      }
    
      const debounceCb = () =>
        debounce(
          () => {
            setOffset();
          },
          100,
          { maxWait: 300 }
        )();
    
      // INIT
      function init() {
        addCarouselControls();
        addLiveRegion();
        goToSlide(0);
        announceCurrentSlide();
        showImageInformation();
    
        // Re-align on resize.
        window.addEventListener('resize', debounceCb);
      }
    
      // DESTROY
      function destroy() {
        removeCarouselControls();
        removeLiveRegion();
        window.removeEventListener('resize', debounceCb);
      }
    
      init();
    
      // REVEAL API
      return {
        init,
        destroy,
      };
    };
    
    // module exports
    export default carousels;
    
  • URL: /components/raw/ecl-carousels/carousels.js
  • Filesystem Path: framework/components/ecl-carousels/carousels.js
  • Size: 4.5 KB
  • Content:
    /**
     * Carousel
     * @define carousel ; weak
     */
    
    .ecl-carousel {
      align-items: stretch;
      background-color: #000;
      display: flex;
      flex-direction: column;
      margin: 0;
      max-width: 100%;
      overflow: hidden;
    
      @include ecl-media-breakpoint-up('xl') {
        flex-direction: row;
        max-height: 100vh;
      }
    }
    
    // When carousel is a dialog.
    .ecl-carousel[aria-hidden='true'] {
      display: none;
    }
    
    .ecl-carousel[aria-hidden='false'] {
      display: flex;
      height: 90vh;
      left: 3%;
      position: absolute;
      top: 3%;
      width: 90%;
      z-index: map-get($ecl-z-index, 'modal');
    }
    
    .ecl-carousel__list-wrapper {
      max-height: 70vh;
      overflow: hidden;
      position: relative;
    }
    
    @include ecl-media-breakpoint-up('xl') {
      .ecl-carousel__list-wrapper {
        flex: 1;
        max-height: 100%;
      }
    }
    
    .ecl-carousel__controls {
      margin: 0;
    }
    
    .ecl-carousel__list {
      display: flex;
      margin: 0;
      white-space: nowrap;
      width: 100%;
    }
    
    .ecl-carousel__item {
      flex: 1 0 100%;
      position: relative;
    }
    
    .ecl-carousel__image {
      display: block;
      margin: auto;
    }
    
    .ecl-carousel__button {
      @include ecl-focus-outline-border();
    
      background-color: #000;
      border: 0;
      color: #fff;
      font-size: map-get($ecl-font-size, 'xxl');
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      z-index: map-get($ecl-z-index, 'navigation');
    }
    
    .ecl-carousel__button--previous {
      left: 0;
    }
    
    .ecl-carousel__button--next {
      right: 0;
    }
    
    // JS will show only the necessary one by a data attribute.
    .ecl-carousel__image-information {
      display: none;
      text-align: left;
    }
    
    .ecl-carousel__live-region {
      background-color: map-get($ecl-colors, 'grey-100');
      color: #fff;
      min-width: 30%;
      padding: map-get($ecl-spacing, 'l');
    
      a {
        color: #fff;
      }
    }
    
    @include ecl-media-breakpoint-up('xl') {
      .ecl-carousel__live-region {
        flex: 0;
        max-height: 100%;
      }
    }
    
    .ecl-carousel__meta {
      margin-bottom: map-get($ecl-spacing, 'l');
    }
    
    // Every link has specific styling.
    .ecl-carousel__meta-link {
      padding-right: map-get($ecl-spacing, 'm');
      position: relative;
    
      // put icon on right without extends
      &::before {
        position: absolute;
        right: 0;
      }
    }
    
    .ecl-carousel__meta-item {
      display: inline-block;
      margin-right: map-get($ecl-spacing, 'xs');
      padding-bottom: map-get($ecl-spacing, 'xxxs');
    }
    
    .ecl-carousel__meta-slide {
      order: -1;
      padding-top: map-get($ecl-spacing, 's');
    }
    
    .ecl-carousel__image-copyright {
      font-size: map-get($ecl-font-size, 'xxs');
    }
    
    /* Show information in a similar flow as if there were js. */
    .no-js {
      .ecl-carousel__list-wrapper {
        overflow-x: initial;
      }
    
      .ecl-carousel__list {
        align-items: center;
        height: 100%;
        justify-content: flex-start;
      }
    
      .ecl-carousel__item {
        opacity: 1;
      }
    
      .ecl-carousel__image-information {
        border-bottom: 1px solid #fff;
        display: block;
        margin-bottom: map-get($ecl-spacing, 's');
        padding-bottom: map-get($ecl-spacing, 's');
      }
    
      .ecl-carousel__live-region {
        overflow-y: auto;
      }
    }
    
  • URL: /components/raw/ecl-carousels/ecl-carousels.scss
  • Filesystem Path: framework/components/ecl-carousels/ecl-carousels.scss
  • Size: 3 KB
  • Handle: @ec-europa/ecl-carousels
  • Tags: molecule
  • Preview:
  • Filesystem Path: framework/components/ecl-carousels/ecl-carousels.twig