Guidance

Use the gallery component within content areas or the @uol-widget-gallery to display single or groups of images, videos or combinations of both images and videos. Images and videos can also have an accompanying caption.

In addition to the image or video each item can have an accompanying title and text content.

Clicking on the item expand button will open the item in a full-screen carousel. If a title and text content is available these can be accessed by clicking in the “I” button for the corresponding carousel item.

For videos YouTube is currently the only supported platform.

When to use

Use the Gallery component when you want to present images or videos and have them viewable in full screen.

When not to use

  • Do not use to display essential information as content may be missed within the carousel layout.
  • Do not use for the following content types
    • Graphs
    • Maps
  • Do not use when very high resolution images are needed and the user needs to see the image at full natural resolution.

Developer guidance

Images

For performance and image quality it is recommended that a lower and higher resolution version of each image is provided. e.g.

"img": {
  "srcHighQuality": "/placeholders/campus/full/29940.jpeg",
  "src": "/placeholders/campus/medium/29940.jpeg",
  ...
},
  • Lower resolution image width 1200px
  • Higher resolution image maximum 2500px on longest axis.

For photographic images JPEG compression should be set to 85% quality

For graphical images PNG format should be used.

Videos

Ensure you set the item type to video.

At this point the only video host supported is YouTube. You can use full youtube URLS or the shortened versions. Eg. Both the following will display the YouTube video with id PpilTVi5Yk4

Do not use the ‘embed’ code that YouTube provides as this will not work within the @uol-gallery component. The component makes use of the oEmbed API that YouTube provides.

Do not provide an image for videos as video images are retrieved directly from YouTube. You may provide a caption.

{
  "title": "Ear tickle therapy and the world's thinnest gold",
  "type": "video",
  "video": "https://www.youtube.com/watch?v=PpilTVi5Yk4",
  "img": {
    "caption": "Ear tickle therapy and the world's thinnest gold",
  },
  ...
}

A single video with alternate play icon

In certain scenarios (following guidance from the design team) there maybe a need to replace the usual ‘expanded’ icon for the larger orange one with the play button icon.

This can be done through the config by setting the ‘videoPlayIcon: true’:

{
  "name": "Single video with play icon",
  "label": "Single video with play icon",
  "context": {
    "gallery": {
      "headingLevel": 2,
      "videoPlayIcon": true,
      "items": galleryVideos.slice(2, 3),
    },
  },
}

Please see the ‘with video’ variant of the @featured-content for more details.


{% if gallery.items.length %}
  {% set headingTag = 'h' + gallery.headingLevel if gallery.headingLevel else 'h3' %}
  <section class="uol-gallery-container" aria-label="Gallery of {{ gallery.items.length }} items">
    <div class="uol-gallery uol-gallery--count-{{ gallery.items.length }}">
      {% for item in gallery.items %}
        <div class="uol-gallery__item {{ 'uol-gallery__item--' + item.type if item.type }} {{ 'uol-gallery__item--image' if item.img.src }} {{ 'uol-gallery__item--video-play-icon' if gallery.videoPlayIcon }}"
        {% if item.type and item.video %} data-video="{{ item.video }}" {% endif %}>
          <{{ headingTag }} class="uol-gallery__item__title">{{ item.title | safe }}</{{ headingTag }}>
          <figure class="uol-gallery__figure">
            <div class="uol-gallery__image-container">
              <img
                src="{{ item.img.src if item.img.src else '#' }}"
                alt="{{ item.img.alt }}"
                {% if item.img.srcHighQuality %} data-src-high-quality="{{ item.img.srcHighQuality }}" {% endif %}>
            </div>
            {% if item.img.caption %}
              <figcaption class="uol-gallery__image-caption">{{ item.img.caption | safe }}</figcaption>
            {% endif %}
          </figure>
          {% if item.type and item.video %}
            <noscript>
              <a href="{{ item.video }}">{{ item.video }}</a>
            </noscript>
          {% endif %}
          {% if item.content %}
            <div class="uol-gallery__item__content">
              {{ item.content | safe }}
            </div>
          {% endif %}
        </div>
      {% endfor %}
    </div>
  </section>
{% endif %}
<section class="uol-gallery-container" aria-label="Gallery of 6 items">
    <div class="uol-gallery uol-gallery--count-6">

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://www.youtube.com/watch?v=PpilTVi5Yk4">
            <h2 class="uol-gallery__item__title">Ear tickle therapy, the world's thinnest gold and Hajj regulation</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">Ear tickle therapy, the world's thinnest gold and Hajj regulation</figcaption>

            </figure>

            <noscript>
                <a href="https://www.youtube.com/watch?v=PpilTVi5Yk4">https://www.youtube.com/watch?v=PpilTVi5Yk4</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>Lorem ipsum <a href='/some-text-link'>dolor sit amet consectetur</a> adipisicing elit. Corrupti, quasi nostrum blanditiis hic totam a id architecto molestias, sunt vitae iste consectetur cupiditate incidunt autem illo, consequuntur recusandae? Ipsam, asperiores.</p>
                <p>Expedita saepe illo vero sit et! Eveniet, deserunt. Nihil omnis fugit ut veniam ullam, non maiores, consequatur amet enim dolore totam, laborum accusantium voluptatum iure est ab aspernatur reiciendis explicabo.</p>
                <p>Eos reprehenderit suscipit, at eveniet, minus ea quod quis provident, nisi fugit maiores molestias culpa. Rerum rem pariatur quo mollitia autem omnis eum officiis, natus beatae eos saepe culpa earum!</p>
                <p>Eum ut tempore delectus quos unde tenetur neque perspiciatis. Dicta sunt rem dolore, in ab impedit assumenda, quaerat neque quos veritatis consequatur accusantium dignissimos eius natus iusto nostrum eos maxime.</p>
            </div>

        </div>

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://www.youtube.com/watch?v=_-hZg6E6OOc">
            <h2 class="uol-gallery__item__title">Edible bugs, paying for parks, animals and climate change</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">Edible bugs, paying for parks, animals and climate change</figcaption>

            </figure>

            <noscript>
                <a href="https://www.youtube.com/watch?v=_-hZg6E6OOc">https://www.youtube.com/watch?v=_-hZg6E6OOc</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>Lorem ipsum <a href='/some-text-link'>dolor sit amet consectetur</a> adipisicing elit. Corrupti, quasi nostrum blanditiis hic totam a id architecto molestias, sunt vitae iste consectetur cupiditate incidunt autem illo, consequuntur recusandae? Ipsam, asperiores.</p>
                <p>Expedita saepe illo vero sit et! Eveniet, deserunt. Nihil omnis fugit ut veniam ullam, non maiores, consequatur amet enim dolore totam, laborum accusantium voluptatum iure est ab aspernatur reiciendis explicabo.</p>
                <p>Eos reprehenderit suscipit, at eveniet, minus ea quod quis provident, nisi fugit maiores molestias culpa. Rerum rem pariatur quo mollitia autem omnis eum officiis, natus beatae eos saepe culpa earum!</p>
                <p>Eum ut tempore delectus quos unde tenetur neque perspiciatis. Dicta sunt rem dolore, in ab impedit assumenda, quaerat neque quos veritatis consequatur accusantium dignissimos eius natus iusto nostrum eos maxime.</p>
            </div>

        </div>

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://www.youtube.com/watch?v=j4pt8l7e3u8">
            <h2 class="uol-gallery__item__title">Studying in a different country</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">Studying in a different country</figcaption>

            </figure>

            <noscript>
                <a href="https://www.youtube.com/watch?v=j4pt8l7e3u8">https://www.youtube.com/watch?v=j4pt8l7e3u8</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>Being an international student can be nerve-wracking, but at the University of Leeds we're here to help every step of the way. If you're studying in a new country, there's lots to get your head around, but lots of opportunities too, to make new friends and try new experiences. </p>
                <p>Every year more than 6,000 international students choose to study with us, making our campus one of the most diverse and multicultural in the UK. </p>
                <p>Join our new Virtual Open Days: <a href='https://virtualopenday.leeds.ac.uk'>https://virtualopenday.leeds.ac.uk</a></p>
            </div>

        </div>

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://www.youtube.com/watch?v=Ej7oXq_AHCU">
            <h2 class="uol-gallery__item__title">You Can Master with the University of Leeds</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">You Can Master with the University of Leeds</figcaption>

            </figure>

            <noscript>
                <a href="https://www.youtube.com/watch?v=Ej7oXq_AHCU">https://www.youtube.com/watch?v=Ej7oXq_AHCU</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>Whether you're continuing from undergraduate study, returning to education or changing direction, a postgraduate degree from the University of Leeds can help you take the next step. We'll challenge you to explore, question and discover.</p>
                <p>You can Master with the University of Leeds.</p>
                <p>Start your journey to postgraduate study today: <a href='https://www.leeds.ac.uk/info/101000/masters_courses'>Masters Courses</a></p>
            </div>

        </div>

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://youtu.be/_zbLlZ10R5Y">
            <h2 class="uol-gallery__item__title">Understanding the past, shaping the present and innovating for the future with research at Leeds</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">Understanding the past, shaping the present and innovating for the future with research at Leeds</figcaption>

            </figure>

            <noscript>
                <a href="https://youtu.be/_zbLlZ10R5Y">https://youtu.be/_zbLlZ10R5Y</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>This month, we're deepening our understanding of the past, present and future through research.
                <p>
                <p>Preserving invaluable cultural legacies for future generations</p>
                <p>Children playing wallops (nine-pins) in the street at Castle Bolton in Wensleydale, North Yorkshire, 1964. A group of men can be seen sitting on a bench, watching men from the village playing quoits on the grass verge.</p>
                <p>The University's Dialect and Heritage project has been awarded £530,500 from The National Lottery Heritage Fund to open up the extensive Leeds Archive of Vernacular Culture (LAVC) to the public. </p>
                <p>As well as the National Lottery Heritage Fund £530,500 grant, the University of Leeds’ Footsteps Fund and other alumni donations have contributed almost £110,000 to the project, plus up to £23,000 from the partner museums.</p>
                <p>The archive includes the ground-breaking work of the Survey of English Dialects, carried out by nine intrepid fieldworkers in the 1950s and is held in Leeds University Library’s Special Collections. The survey will now be updated and made available online for the first time.</p>
                <p>Skipping breakfast linked to lower GCSE grades</p>
                <p>Researchers, from the University of Leeds, have for the first time demonstrated a link between eating breakfast and GCSE performance for secondary school students in the UK.</p>
                <p>Adding together all of a student’s exam results, they found that students who said they rarely ate breakfast achieved nearly two grades lower than those who rarely missed their morning meal.</p>
                <p>The findings were covered extensively in the press, including on The Telegraph, The Mirror and the Daily Mail. </p>
                <p>Pioneering new radiotherapy technologies</p>
                <p>Leeds scientists and clinicians have been awarded a major cash boost from Cancer Research UK to pioneer new radiotherapy technologies that could help more people in Yorkshire survive cancer.</p>
                <p>Professor David Sebag-Montefiore standing next to a radiotherapy machine in a blue room treatment room in a hospital in Leeds.</p>
                <p>Experts from the University of Leeds and Leeds Teaching Hospitals NHS Trust are set to receive £3.5 million over the next five years to fund advances in radiotherapy research, including the use of artificial intelligence with imaging technology.</p>
            </div>

        </div>

        <div class="uol-gallery__item uol-gallery__item--video  " data-video="https://youtu.be/y28L6rzglNY">
            <h2 class="uol-gallery__item__title">New cancer research, heart attack care and superbug innovation</h2>
            <figure class="uol-gallery__figure">
                <div class="uol-gallery__image-container">
                    <img src="#" alt="">
                </div>

                <figcaption class="uol-gallery__image-caption">New cancer research, heart attack care and superbug innovation</figcaption>

            </figure>

            <noscript>
                <a href="https://youtu.be/y28L6rzglNY">https://youtu.be/y28L6rzglNY</a>
            </noscript>

            <div class="uol-gallery__item__content">
                <p>October roundup of some the latest research and education stories from the University.</p>
                <p>Improving cancer treatments and patient care through collaboration</p>
                <p>Researchers in Yorkshire are collaborating on a new national data hub that aims to transform how cancer data from across the UK can be used to improve patient care.</p>
                <p>Collaborators from Yorkshire and Humber include the University of Leeds, Leeds Teaching Hospitals NHS Trust, University of Sheffield, Sheffield Teaching Hospitals, Sheffield Children’s Hospitals, Yorkshire and Humber Local Health Care Record, and Yorkshire & Humber Academic Health Science Network. </p>
                <p>The work has been awarded £4.5 million to deliver DATA-CAN, the Health Data Research Hub for Cancer. </p>
                <p>Another exciting Light Night in Leeds</p>
                <p>Art and biology met on campus for another year of Light Night. </p>
                <p>Light Night is the UK’s largest annual arts and light festival. It will see the transformation of some of Leeds' most recognisable indoor and outdoor spaces during two special nights.</p>
                <p>Visitors were invited to step inside The Nectary - a series of huge, glowing flower heads, carefully crafted from recycled plastic and hung from trees in University Square - amongst other amazing cultural attractions on campus.</p>
                <p>Leeds scientists reveal gender gaps in heart attack care </p>
                <p>In partnership with the British Heart Foundation, Leeds researchers have helped to highlight the inequalities in awareness, diagnosis, treatment and care for women suffering a heart attack. </p>
                <p>Professor Chris Gale's research looked at the disparity in care for men and women after experiencing a heart attack. The new report was covered widely, including extensively on the BBC.</p>
                <p>Scientists have uncovered a novel approach to prevent and treating a widespread superbug</p>
                <p>Helicobacter pylori, a bacterial pathogen carried by 4.4 billion people worldwide</p>
                <p>Working with researchers across the UK and in Germany, Leeds scientists have developed an approach to tackling a widespread superbug using natural ingredients.</p>
                <p>The bug, Helicobacter pylori (H. pylori), is a bacterial pathogen carried by 4.4 billion people worldwide, with the highest prevalence in Africa, Latin America and the Caribbean.</p>
                <p>Although the majority of infections show no symptoms, if left untreated the pathogen can cause chronic inflammation of the stomach lining, ulcers and is associated with an increased risk of gastric cancer.</p>
                <p>Current treatments involve multi-target therapy with a combination of antibiotics, but this has promoted the emergence of resistant strains.</p>
                <p>Scientists have now uncovered a novel antibiotic-free approach using only food- and pharmaceutical-grade ingredients.</p>
            </div>

        </div>

    </div>
</section>
  • Content:
    $nav-depth: 77px;
    // button sizes used in calculation for right hand nav spacings
    $small-button-size: 2.81rem;
    $standard-button-size: 3.12rem;
    
    @keyframes info-text-fade {
      0% {
        display: none;
        opacity: 0;
      }
      1% {
        display: block;
        opacity: 0;
        // transform: scale(0);
      }
      30% {
        display: block;
        opacity: 0;
        // transform: scale(0);
      }
      100% {
        display: block;
        opacity: 1;
        // transform: scale(1);
      }
    }
    
    .uol-gallery-modal {
      position: absolute;
      width: 100vw;
      height: calc(var(--vh, 1vh) * 99.9); // 99.9% to avoid distortion when zoomed in
      left: 0;
      top: 0;
      background: $color-black--dark;
      z-index: 10;
      color: $color-white;
    }
    
      .uol-gallery-modal__button-close {
        .js .uol-gallery-modal & {
          position: absolute;
          top: $spacing-4;
          right: $spacing-3;
          z-index: 2;
    
          @media (orientation: portrait) {
            @include media(">=uol-media-xs") {
              right: $spacing-5;
            }
          }
    
          @media (orientation: landscape) {
            top: $spacing-2;
            right: ($nav-depth / 2);
            transform: translateX(50%);
            
            @include media(">=uol-media-s") {
              top: $spacing-5;
            }
          }
    
          svg {
            path {
              fill: $color-white;
    
              @media (-ms-high-contrast: active), (forced-colors: active) {
                fill: ButtonText;
              }
            }
          }
        }
      }
    
      .uol-gallery-modal__track {
        margin: 0;
        padding: 0;
        height: calc(var(--vh, 1vh) * 99.9);
        width: 100vw;
        list-style: none;
        scroll-snap-type: x mandatory;
        display: flex;
        flex-wrap: wrap;
        flex-direction: column;
        overflow-x: auto;
    
        &::-webkit-scrollbar {
          display: none;
        }
    
        // TODO: IE 11 remove scrollbar
        -ms-overflow-style: none;
      }
    
        .uol-gallery-modal__track--smooth {
          scroll-behavior: smooth;
        }
    
      .uol-gallery-modal__item {
        position: relative;
        box-sizing: border-box;
        display: flex;
        width: 100vw;
        height: 100%;
        scroll-snap-align: start;
        background: $color-black;
    
        @media (orientation: portrait) {
          margin-top: $nav-depth;
          height: calc(100% - #{$nav-depth});
        }
      }
    
      .uol-gallery-modal__info-container {
        box-sizing: border-box;
        background: rgba($color-black--dark, 0.92);
        z-index: 1;
        transition: all 0.3s ease;
    
        @media (orientation: landscape) {
          position: absolute;
          width: $nav-depth;
          height: 100%;
          border-right: 3px solid $color-border--light;
          display: flex;
    
          &::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            bottom: 0;
            width: calc(#{$nav-depth} - 3px);
            background-color: $color-black--dark;
          }
        }
    
        @media (orientation: portrait) {
          position: absolute;
          bottom: 0;
          width: 100%;
          max-height: 100%;
          overflow-y: hidden;
          border-top: 3px solid $color-border--light;
          padding: $spacing-4 $spacing-5;
        }
      }
    
        .uol-gallery-modal__info-container--open {
    
          @media (orientation: portrait) {
            padding-bottom: $spacing-6;
          }
    
          @media (orientation: landscape) {
            width: calc(100vw - (#{$nav-depth} * 1.5));
    
            @include media(">=uol-media-s") {
              width: calc(80vw - (#{$nav-depth}));
            }
    
            @include media(">=uol-media-m") {
              width: calc(70vw - (#{$nav-depth}));
            }
    
            @include media(">=uol-media-l") {
              width: calc(60vw - (#{$nav-depth}));
            }
    
            @include media(">=uol-media-xl") {
              width: calc(50vw - (#{$nav-depth}));
            }
          }
        }
    
      .uol-gallery-modal__button-info {
        @media (orientation: landscape) {
          position: absolute;
          left: $spacing-4;
          top: $spacing-2;
    
          @include media(">=uol-media-s") {
            top: $spacing-5;
          }
    
          @include media(">=uol-media-l") {
            left: $spacing-3;
          }
        }
    
        .uol-gallery-modal__info-container--open & {
          border: 2px solid $color-white;
        }
      }
    
      .uol-gallery-modal__info {
        @include ds-scrollbars();
    
        display: none;
        box-sizing: border-box;
        overflow-y: auto;
    
        @media (orientation: portrait) {
          margin-top: $spacing-4;
          max-height: calc(var(--vh, 1vh) * 100 - (#{$nav-depth} * 2) - #{$spacing-6});
          padding-right: $spacing-4;
        }
    
        @media (orientation: landscape) {
          box-sizing: border-box;
          flex-basis: calc(100% - #{$nav-depth});
          margin: $spacing-5 $spacing-2 0 auto;
          padding-right: $spacing-2;
          padding-left: $spacing-4;
        }
    
        .uol-gallery-modal__button-info[aria-expanded="true"] + & {
          display: block;
    
          @media (orientation: landscape) {
            animation: info-text-fade 0.7s ease;
            animation-fill-mode: both;
          }
        }
      }
    
        .uol-gallery-modal__info__title {
          @extend .uol-typography-heading-2;
    
          margin-top: 0;
          margin-bottom: $spacing-4;
    
          @media (orientation: landscape) {
            margin-top: $spacing-4;
    
            @include media(">=uol-media-m") {
              margin-top: $spacing-2;
            }
          }
        }
    
      .uol-gallery-modal__figure {
        position: relative;
        display: flex;
        flex-wrap: wrap;
        width: 100%;
        height: 100%;
        justify-content: center;
        align-items: center;
    
        @media (orientation: landscape) {
          height: 100%;
          width: calc(100% - #{$nav-depth});
    
          .uol-gallery-modal__info-container + & {
            margin-left: $nav-depth;
            width: calc(100% - (#{$nav-depth} * 2));
          }
        }
    
        @media (orientation: portrait) {
          .uol-gallery-modal__item--has-info & {
            height: calc(100% - #{$nav-depth} - 3px);
          }
        }
      }
    
      .uol-gallery-modal__image-container {
        position: absolute;
        top: 50%;
        height: 100%;
        right: 0;
        bottom: 0;
        left: 0;
        transform: translateY(-50%);
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        align-items: center;
    
        img {
          max-width: 100%;
          max-height: 100%;
          object-fit: contain;
        }
      }
    
        .uol-gallery-modal__image-container--video {
          top: auto;
          transform: none;
          position: relative;
          aspect-ratio: 16/9;
          overflow: hidden;
    
          iframe {
            border: none;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          }
        }
    
      .uol-gallery-modal__image-caption {
        @extend %text-size-caption;
    
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        padding: $spacing-2 $spacing-4;
        background: rgba($color-black, 0.88);
        color: $color-white;
    
        // TODO: This is a hack to stop caption overlapping video controls
        @media (orientation: landscape) {
          .uol-gallery-modal__figure--video & {
            display: none;
          }
        }
      }
    
      .uol-gallery-modal__nav-container {
        box-sizing: border-box;
        position: fixed;
        background: $color-black--dark;
        display: flex;
        z-index: 1;
    
        @media (orientation: landscape) {
          top: 0;
          right: 0;
          width: $nav-depth;
          flex-direction: column;
          height: 100%;
          padding: $small-button-size + $spacing-2 $spacing-3 0;
          
          @include media(">=uol-media-s") {
            padding: $small-button-size + $spacing-5 $spacing-3 0;
          }
          
          @include media(">=uol-media-l") {
            padding: $standard-button-size + $spacing-5 $spacing-3 0;
          }
    
          button {
            margin-bottom: $spacing-2;
            margin-left: auto;
            margin-right: auto;
    
            @include media(">=uol-media-s") {
              margin-bottom: $spacing-5;
              padding: $spacing-9 $spacing-3 $spacing-4;
            }
          }
        }
    
        @media (orientation: portrait) {
          top: 0;
          height: $nav-depth;
          width: 100%;
          justify-content: center;
          align-items: center;
        }
      }
    
      .uol-gallery-modal__progress {
        font-variant-numeric: lining-nums;
    
        @media (orientation: landscape) {
          order: -1;
          text-align: center;
          margin: $spacing-2 0;
          
          @include media(">=uol-media-s") {
            margin: $spacing-5 0;
          }
        }
    
        @media (orientation: portrait) {
          margin: 0 $spacing-5;
    
          @include media(">=uol-media-xs") {
            margin: 0 $spacing-7;
          }
    
          @include media(">=uol-media-s") {
            margin: 0 $spacing-8;
          }
        }
      }
    
        .uol-gallery-modal__progress__current,
        .uol-gallery-modal__progress__total {
          position: relative;
          top: 0.05em;
        }
    
        .uol-gallery-modal__progress__current {
          color: $color-brand--bright;
        }
    
  • URL: /components/raw/uol-gallery/_gallery-modal.scss
  • Filesystem Path: src/library/02-components/gallery/_gallery-modal.scss
  • Size: 8.6 KB
  • Content:
    @mixin galleryItemHalf {
      flex-basis: calc(50% - #{$spacing-4});
      margin: 0 $spacing-2 $spacing-4;
    
      @include media(">=uol-media-l") {
        flex-basis: calc(50% - #{$spacing-5});
        margin: 0 $spacing-3 $spacing-4;
      }
    
      @include media(">=uol-media-xl") {
        flex-basis: calc(50% - #{$spacing-6});
        margin: 0 $spacing-4 $spacing-5;
      }
    }
    
    // Keyframe animations
    @keyframes skeletonBg {
      0% {
        background-color: $color-grey--light;
      }
      50% {
        background-color: rgba($color-grey--light, 0.2);
      }
      100% {
        background-color: $color-grey--light;
      }
    }
    
    @keyframes galleryImageFadeIn {
      0% {
        display: none;
        opacity: 0;
      }
      1% {
        display: block;
      }
      100% {
        display: block;
        opacity: 1;
      }
    }
    
    .uol-gallery-container {
      width: 100%;
      overflow-x: hidden;
    }
    
    .uol-gallery {
      display: flex;
      flex-wrap: wrap;
      margin-left: -#{$spacing-2};
      margin-right: -#{$spacing-2};
    
      @include media(">=uol-media-l") {
        margin-left: -#{$spacing-3};
        margin-right: -#{$spacing-3};
      }
    
      @include media(">=uol-media-xl") {
        margin-left: -#{$spacing-4};
        margin-right: -#{$spacing-4};
      }
    
      .uol-rich-text & {
        max-width: none;
      }
    }
    
      .uol-gallery__item {
        box-sizing: border-box;
    
        .js & {
          @include galleryItemHalf;
        }
    
        &:first-of-type {
          flex-basis: 100%;
        }
    
        .uol-gallery--count-2 &,
        .uol-gallery--count-4 & {
    
          &:first-of-type {
            @include galleryItemHalf;
          }
        }
    
        // Hide all after 5
        &:nth-of-type(5) ~ & {
          .js & {
            display: none;
          }
        }
      }
    
      .uol-gallery__item__title {
        .js & {
          @extend .hide-accessible;
        }
      }
    
      .uol-gallery__figure {
    
        .uol-gallery__item--video & {
          display: none;
    
          .js & {
            display: block;
          }
        }
    
        .uol-rich-text & {
          margin: 0;
        }
      }
    
      .uol-gallery__image-caption {
        @extend %text-size-caption;
    
        color: $color-font--light;
        padding-top: $spacing-2;
    
        @include media(">=uol-media-l") {
          padding-top: $spacing-3;
        }
      }
    
      .uol-gallery__image-container {
        font-size: 0;
        @include imageFit(66.6%);
    
        .uol-gallery__item:first-of-type & {
          @include imageFit(50%);
        }
    
        .uol-gallery--count-2 .uol-gallery__item:nth-of-type(2) & {
          @include imageFit(50%);
        }
    
        .uol-gallery--count-4 .uol-gallery__item:first-of-type & {
          @include imageFit(66.6%);
        }
    
        .uol-gallery__item--video & {
          outline: 1px solid rgba($color-grey--dark, 0.7);
          outline-offset: -1px;
          background-color: $color-grey--light;
          animation-name: skeletonBg;
          animation-duration: 2.49s;
          animation-iteration-count: 2;
    
          img {
            opacity: 0;
          }
        }
    
          .uol-gallery__item--video[aria-busy=false] & {
            outline: none;
    
            img {
              opacity: 0;
              display: block;
              animation: galleryImageFadeIn 1s ease-out forwards;
            }
          }
    
        @for $i from 1 through 5 {
          .uol-gallery__item--video:nth-of-type(5n + #{$i}) & {
            animation-delay: #{$i * 0.2}s;
          }
        }
      }
    
      .uol-gallery__item__content {
        .js & {
          display: none;
        }
      }
    
      .uol-gallery__button {
        background: rgba($color-black, 0.75);
        border: none;
      }
    
      .uol-gallery__button--open-item {
        @include button_focus(-3px, false, $color-brand--bright);
    
        position: absolute;
        width: 45px;
        height: 45px;
        border-radius: 50%;
        left: $spacing-2;
        bottom: $spacing-2;
        transition: all 0.15s;
    
        &:hover,
        &:focus {
          background: $color-black;
          color: $color-brand--bright;
        }
    
        @include media(">=uol-media-m") {
          left: $spacing-5;
          bottom: $spacing-5;
        }
    
        // Override the default uol-icon positioning
        .js .uol-gallery__item & {
          position: absolute;
        }
      }
    
      .uol-gallery__button--with-count {
        @include button_focus(-2px, false, $color-brand--bright);
        @extend %text-size-heading-2;
    
        position: absolute;
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: rgba($color-black, 0.65);
        color: $color-white;
        z-index: 2;
        text-decoration-color: $color-brand--bright;
        transition: all 0.15s;
    
        &:hover,
        &:focus {
          background: rgba($color-black, 0.75);
          text-decoration: underline;
          text-decoration-color: $color-brand--bright;
    
          @media (-ms-high-contrast: active), (forced-colors: active) {
            text-decoration-color: ButtonText;
            border: 2px solid ButtonText;
          }
        }
      }
    
    // Update for video variant using play icon
    .js .uol-gallery__item--video-play-icon 
    .uol-button.uol-icon--icon-only {
      @extend %text-size-heading-2;
    
      background: $color-brand;
      border: 2px solid $color-white;
      border-radius: 50%;
      color: $color-white;
      aspect-ratio: 1/1;
      width: $spacing-8;
      height: $spacing-8;
      left: $spacing-5;
      bottom: $spacing-5;
    
      svg {
        width: $spacing-7;
        height: $spacing-7;
      }
    }
  • URL: /components/raw/uol-gallery/_gallery.scss
  • Filesystem Path: src/library/02-components/gallery/_gallery.scss
  • Size: 5 KB
  • Content:
    // TODO: IE11 resizeObserver polyfill
    import ResizeObserver from "resize-observer-polyfill";
    
    /*
     * Set shift keydown listener
     * Needed to overcome tab oder/focus issues when reverse tabbing.
     * See listenGalleryScroll()
    */
    let shiftKeyDown = false;
    function isShiftKeyDown(event) {
      shiftKeyDown = event.shiftKey;
    }
    document.addEventListener("keydown", isShiftKeyDown);
    document.addEventListener("keyup", isShiftKeyDown);
    
    /**
     * Returns the modal outer to contain the new gallery
     * @returns {element} Modal element
     */
    const modalOuter = () => {
      const modal = document.createElement("div");
      modal.setAttribute("role", "dialog");
      modal.setAttribute("tabindex", "-1");
      modal.setAttribute("aria-modal", "true");
      modal.setAttribute("aria-label", "Gallery carousel");
      modal.classList.add("uol-gallery-modal");
    
      return modal;
    };
    
    /**
     * HTML fragment for close button
     */
    const buttonClose = `
      <button class="uol-button uol-icon uol-icon--icon-only uol-icon--mdiClose uol-gallery-modal__button-close" type="button">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" focusable="false" aria-hidden="true">
          <path fill="#000000" fill-rule="nonzero" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"></path>
        </svg>
        <span class="uol-icon__label">Close</span>
      </button>
    `;
    
    /**
     * Create the modal gallery navigation.
     * Returns '' if only 1 item and HTML string otherwise
     * @param {object} items - NodeList of the gallery's initial items
     * @param {number} index - the index of the gallery item to open on
     * @returns {string} HTML for modal navigation
     */
    const galleryNavigation = (items, index) => {
      // if there is only one item we do not need navigation
      if (items.length === 1) return ''
      // Otherwise return navigation
      return `
      <div class="uol-gallery-modal__nav-container" aria-hidden="true">
        <button
          tabindex="-1"
          class="
            uol-gallery-modal__button-nav
            uol-gallery-modal__button-nav--prev
            uol-button uol-button--bright uol-icon uol-icon--icon-only uol-icon--mdiArrowLeft"
          type="button" ${index === 0 ? "disabled" : ""}>
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" focusable="false" aria-hidden="true">
            <path fill="#000000" fill-rule="nonzero" d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"></path>
          </svg>
          <span class="uol-icon__label">Previous</span>
        </button>
        <span class="uol-gallery-modal__progress">
          <span class="uol-gallery-modal__progress__current">${index + 1}</span> /
          <span class="uol-gallery-modal__progress__total">${items.length}</span>
        </span>
        <button tabindex="-1" class="uol-gallery-modal__button-nav uol-gallery-modal__button-nav--next uol-button uol-button--bright uol-icon uol-icon--icon-only uol-icon--mdiArrowRight" type="button" ${
          index === items.length - 1 ? "disabled" : ""
        }>
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" focusable="false" aria-hidden="true">
            <path fill="#000000" fill-rule="nonzero" d="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"></path>
          </svg>
          <span class="uol-icon__label">Next</span>
        </button>
      </div>
      `;
    };
    
    /**
     * Create the list of gallery items
     * @param  {Object} items - NodeList of the gallery's initial items
     * @returns {string} - HTML fragment for gallery items list
     */
    const galleryItems = (items) => {
      return `
        <ol class="uol-gallery-modal__track">
          ${[...items]
            .map((item, idx) => {
              return galleryItem(item, idx, items.length);
            })
            .join("")}
        </ol>
      `;
    };
    
    /**
     * Create the gallery item for inclusion in the list
     * @param  {object} item - item from NodeList of items
     * @param  {number} idx - the index of this item in the Items object
     * @returns {string} - HTML fragment for gallery item
     */
    const galleryItem = (item, idx, itemsLength) => {
      const hasInfo = item.querySelector(".uol-gallery__item__content");
      return `
        <li class="uol-gallery-modal__item ${
          hasInfo ? "uol-gallery-modal__item--has-info" : ""
        }" tabindex="0" aria-label="Item ${idx + 1} of ${itemsLength}">
          ${galleryItemInfo(item, idx)}
          ${galleryItemFigure(item, idx)}
        </li>
      `;
    };
    
    /**
     * Create the gallery item info for inclusion in the gallery item
     * @param  {object} item - item from NodeList of items
     * @param  {number} idx - the index of this item in the Items object
     * @returns {string} - HTML fragment for gallery item info
     */
    const galleryItemInfo = (item, idx) => {
      // Get item title
      const itemTitle = item.querySelector(".uol-gallery__item__title");
      const itemTitleText = itemTitle ? itemTitle.innerText : null;
    
      // Get item text content
      const itemContent = item.querySelector(".uol-gallery__item__content");
      const itemContentHTML = itemContent ? itemContent.innerHTML : "";
    
      // If no info content return empty string
      if (!itemContentHTML) return "";
      // Otherwise return the info fragment
      return `
        <div class="uol-gallery-modal__info-container">
          <button aria-expanded="false" class="uol-gallery-modal__button-info uol-button uol-button--bright uol-icon uol-icon--icon-only uol-icon--icon-only--large uol-icon--mdiInformationVariant" type="button">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" focusable="false" aria-hidden="true">
              <path fill="#000000" fill-rule="nonzero" d="M13.5,4A1.5,1.5 0 0,0 12,5.5A1.5,1.5 0 0,0 13.5,7A1.5,1.5 0 0,0 15,5.5A1.5,1.5 0 0,0 13.5,4M13.14,8.77C11.95,8.87 8.7,11.46 8.7,11.46C8.5,11.61 8.56,11.6 8.72,11.88C8.88,12.15 8.86,12.17 9.05,12.04C9.25,11.91 9.58,11.7 10.13,11.36C12.25,10 10.47,13.14 9.56,18.43C9.2,21.05 11.56,19.7 12.17,19.3C12.77,18.91 14.38,17.8 14.54,17.69C14.76,17.54 14.6,17.42 14.43,17.17C14.31,17 14.19,17.12 14.19,17.12C13.54,17.55 12.35,18.45 12.19,17.88C12,17.31 13.22,13.4 13.89,10.71C14,10.07 14.3,8.67 13.14,8.77Z"></path>
            </svg>
            <span class="uol-icon__label">Show information</span>
          </button>
          <aside class="uol-gallery-modal__info" tabindex="0" aria-labelledby="item-title-${idx}">
            ${
              itemTitle
                ? `<h2 id="item-title-${idx}" class="uol-gallery-modal__info__title">${itemTitleText}</h2>`
                : ""
            }
            ${itemContentHTML}
          </aside>
        </div>
      `;
    };
    
    /**
     * Create the <figure> element the the gallery item
     * @param {object} item - item from NodeList of items
     * @param {number} idx - the index of this item in the Items object
     * @returns {string} - HTML fragment for gallery item figure element
     */
    const galleryItemFigure = (item, idx) => {
      // Get image and attributes
      const itemImage = item.querySelector(".uol-gallery__image-container img");
      const imgAlt = itemImage ? itemImage.getAttribute("alt") : "";
    
      // Set image src to either image data attribute or native src
      let imgSrc
      if ( itemImage.dataset.srcHighQuality ) {
        imgSrc = itemImage.dataset.srcHighQuality;
      } else if (itemImage.getAttribute("src")) {
        imgSrc = itemImage.getAttribute("src");
      }
    
      // Set image caption if present in item
      const caption = item.querySelector(".uol-gallery__image-caption");
      const captionText = caption ? caption.innerText : null;
    
      // Set YouTube constants
      const youtubeUrl = item.dataset.youtubeUrl;
      const youtubeTitle = item.dataset.youtubeTitle;
      const youtubeId = item.dataset.youtubeId;
    
      let figureContent;
      if (youtubeUrl) {
        // As the YouTube iframe breaks the focus trap
        // we include a visually hidden text link so that
        // there is a focusable element after the iframe
        figureContent = `
        <iframe
          class="youtube"
          tabindex="0"
          id="video-${idx}"
          data-youtube-id="${youtubeId}"
          title="YouTube: ${youtubeTitle}"
          src="${youtubeUrl}"
          allow="autoplay"
          loading="lazy"></iframe>
        <a class="uol-gallery-modal__embed-link" href="${youtubeUrl}" target="_blank" rel="noopener">Open <span class="hide-accessible">${youtubeTitle}</span> in new window</a>`;
      } else if (itemImage) {
        figureContent = `<img src="${imgSrc}" alt="${imgAlt}" loading="lazy" />`;
      }
      return `
        <figure class="uol-gallery-modal__figure${
          youtubeUrl ? " uol-gallery-modal__figure--video" : ""
        }">
          <div class="uol-gallery-modal__image-container ${
            youtubeUrl ? "uol-gallery-modal__image-container--video" : ""
          }">
            ${figureContent}
          </div>
            ${
              captionText
                ? `<figcaption class="uol-gallery-modal__image-caption">${captionText}</figcaption>`
                : ""
            }
        </figure>
      `;
    };
    
    /**
     * "Close gallery" tasks
     * @param {object} modal - The current modal gallery
     * @param {object} focusedElementBeforeModal - HTML node that triggered the gallery open
     */
    const closeGalleryModal = (modal, focusedElementBeforeModal) => {
      galleryPageContent.hidden = false;
      modal.remove();
      focusedElementBeforeModal.focus();
      focusedElementBeforeModal.parentElement.scrollIntoView();
    };
    
    /**
     * Container function for listeners
     * @param {object} modal - The current modal gallery
     * @param {object} focusedElementBeforeModal - HTML node that triggered the gallery open
     */
    const addListeners = (modal, focusedElementBeforeModal) => {
      const closeButton = modal.querySelector(".uol-gallery-modal__button-close");
      // Listen for close actions
      listenCloseActions(modal, focusedElementBeforeModal, closeButton);
      // Listen for tab keys
      listenTabKey(modal, closeButton);
      // Listen for button navigation
      listenNavButtons(modal);
      // Listen for swipe and scroll navigation
      listenGalleryScroll(modal);
      // Listen to info buttons clicks
      listenInfoButtons(modal);
    
      listenOrientationChange(modal);
    };
    
    /**
     * Listen for close actions. ie Close button press or escape key
     * @param {object} modal
     * @param {object} focusedElementBeforeModal
     * @param {object} closeButton
     */
    const listenCloseActions = (modal, focusedElementBeforeModal, closeButton) => {
      closeButton.onclick = () => {
        closeGalleryModal(modal, focusedElementBeforeModal);
      };
    
      // Handle escape key
      modal.addEventListener("keydown", (event) => {
        if (event.keyCode === 27) {
          closeGalleryModal(modal, focusedElementBeforeModal);
        }
      });
    };
    
    /**
     * Listen for Tab key and ensure modal focus trap
     */
    const listenTabKey = (modal, closeButton) => {
      const modelItems = modal.querySelectorAll(".uol-gallery-modal__item");
      modal.addEventListener("keydown", (event) => {
        // Handle TAB key to produce a modal trap
        if (event.keyCode === 9) {
          // Find all focusable modal elements
          let focusableElements = modal.querySelectorAll(
            ".uol-gallery-modal__item, .uol-gallery-modal__track button:not([disabled]), .uol-gallery-modal__track a"
          );
    
          // We're only interested in visible elements so we create a new array containing only those elements where the offsetWidth !== 0
          let visibleFocusableElements = [];
          focusableElements.forEach((element) => {
            if (element.offsetWidth !== 0) {
              visibleFocusableElements.push(element);
            }
          });
    
          // Define the last focusable element
          const lastElementOfModal =
            visibleFocusableElements[visibleFocusableElements.length - 1];
    
          // If last item in gallery carousel
          if (document.activeElement === lastElementOfModal) {
            event.preventDefault();
            closeButton.focus();
          }
    
          // Shift + Tab on close button - return to last slide
          if (event.shiftKey && document.activeElement === closeButton) {
            event.preventDefault();
            modelItems[modelItems.length - 1].focus();
          }
        }
      });
    };
    
    /**
     * Listen to navigation buttons and scroll the gallery track to the new item
     * @param {Element} modal
     */
    const listenNavButtons = (modal) => {
      const navButtons = modal.querySelectorAll(".uol-gallery-modal__button-nav");
      const navButtonPrev = modal.querySelector(
        ".uol-gallery-modal__button-nav--prev"
      );
      const navButtonNext = modal.querySelector(
        ".uol-gallery-modal__button-nav--next"
      );
      const modalTrack = modal.querySelector(".uol-gallery-modal__track");
      const modelItems = modalTrack.querySelectorAll(".uol-gallery-modal__item");
      const counter = modal.querySelector(".uol-gallery-modal__progress__current");
    
      navButtons.forEach((navButton) => {
        navButton.onclick = () => {
          let current = parseInt(counter.innerText) - 1;
          if (navButton == navButtonPrev) {
            current--;
          } else if (navButton == navButtonNext) {
            current++;
          }
    
          // Scroll to item
          modalTrack.scrollLeft = modelItems[current].offsetLeft;
    
          // Update counter
          counter.innerText = current + 1;
    
          if (current === 0) {
            navButtonPrev.disabled = true;
            navButtonNext.disabled = false;
          } else if (current === modelItems.length - 1) {
            navButtonNext.disabled = true;
            navButtonPrev.disabled = false;
          } else {
            navButtonNext.disabled = false;
            navButtonPrev.disabled = false;
          }
        };
      });
    };
    
    /**
     * Listen for modal track scroll and:
     * - Update current item display
     * - Update previous and next buttons
     * - Pause offscreen YouTube videos
     * @param {Element} modal
     */
    const listenGalleryScroll = (modal) => {
      // TODO: Add all listeners to wrapper function and declare these all once
      const modalTrack = modal.querySelector(".uol-gallery-modal__track");
      const modelItems = modalTrack.querySelectorAll(".uol-gallery-modal__item");
      const counter = modal.querySelector(".uol-gallery-modal__progress__current");
      const navButtonPrev = modal.querySelector(
        ".uol-gallery-modal__button-nav--prev"
      );
      const navButtonNext = modal.querySelector(
        ".uol-gallery-modal__button-nav--next"
      );
    
      let isScrolling = null;
      modalTrack.addEventListener("scroll", () => {
        window.clearTimeout(isScrolling);
    
        isScrolling = setTimeout(() => {
          modelItems.forEach((item, itemIndex) => {
            let itemLeft = item.getBoundingClientRect().x;
            if (!itemLeft)
              // Handle IE11
              itemLeft = item.getBoundingClientRect().left;
    
            // If item is more than half in view
            if (itemLeft >= -10 && itemLeft < modalTrack.clientWidth / 2) {
              // Update counter text
              counter.innerText = itemIndex + 1;
    
              /*
               * Focus on current item unless the shift key is down
               * to overcome tab oder/focus issues when reverse tabbing.
               */
              if (!shiftKeyDown) {
                item.focus();
              }
    
              // Update buttons on scroll
              if (itemIndex === 0) {
                navButtonPrev.disabled = true;
                navButtonNext.disabled = false;
              } else if (itemIndex === modelItems.length - 1) {
                navButtonNext.disabled = true;
                navButtonPrev.disabled = false;
              } else {
                navButtonNext.disabled = false;
                navButtonPrev.disabled = false;
              }
    
              // Pause all youtube videos
              pauseYouTubeVideos(modal);
            }
          });
        }, 100);
      });
    };
    
    const pauseYouTubeVideos = (modal) => {
      const videoIframes = modal.querySelectorAll('iframe')
    
      videoIframes.forEach((iframe) => {
    
        // Check if loaded after lazy by seeing if postMessage is available
        const iframeDoc = iframe.contentWindow;
    
        // Only post message if iframe accepts postMessage
        // NB: Relies on undocumented YouTube postMessage API.
        // If this stops working we will need to replace with the YouTube iframe API
        if ( iframeDoc.postMessage ) {
          iframe.contentWindow.postMessage(
            JSON.stringify({ event: "command", func: "pauseVideo" }),
            "https://www.youtube-nocookie.com"
          );
        }
    
      });
    }
    
    const listenInfoButtons = (modal) => {
      const infoButtons = modal.querySelectorAll(".uol-gallery-modal__button-info");
    
      infoButtons.forEach((infoButton) => {
        const infoParent = infoButton.closest(".uol-gallery-modal__info-container");
    
        infoButton.onclick = () => {
          let expanded =
            infoButton.getAttribute("aria-expanded") === "true" || false;
    
          infoButton.setAttribute("aria-expanded", !expanded);
    
          if (!expanded) {
            infoParent.classList.add("uol-gallery-modal__info-container--open");
          } else {
            infoParent.classList.remove("uol-gallery-modal__info-container--open");
          }
        };
      });
    };
    
    /**
     * Listen for orientation changes and move the info container
     * to ensure correct tab order
     * @param {*} modal
     */
    const listenOrientationChange = modal => {
      // Get the gallery items
      const items = modal.querySelectorAll(".uol-gallery-modal__item");
    
      // Create the query list.
      const mediaQueryList = window.matchMedia("(orientation: portrait)");
    
      // Define a callback function for the event listener.
      function handleOrientationChange(mql) {
    
        items.forEach((item) => {
          // Get the info container
          const itemInfo = item.querySelector(
            ".uol-gallery-modal__info-container"
          );
    
          // If item contains info container
          if (itemInfo) {
            // if portrait orientation
            if (mql.matches) {
              item.insertAdjacentElement("beforeend", itemInfo);
            } else {
              item.insertAdjacentElement("afterbegin", itemInfo);
            }
          }
        });
      }
    
      // Run the orientation change handler once.
      handleOrientationChange(mediaQueryList);
    
      // Add the callback function as a listener to the query list.
      // TODO: IE11 support - revert to addEventListener when we drop is
      // mediaQueryList.addEventListener("change", handleOrientationChange);
      mediaQueryList.addListener(handleOrientationChange);
    }
    
    const galleryPageContent = document.querySelector(".site-outer");
    
    /**
     * Add open modal buttons to each item in initial gallery.
     * @param {NodeList} items
     */
    const galleryAddButtons = (items) => {
      items.forEach((item, index) => {
        const button = document.createElement("button");
        button.classList.add("uol-gallery__button");
    
        // Set basic buttonText
        let buttonText = `Open gallery at item ${ index + 1 } of ${items.length}`
    
        // Enhance buttonText
        // If item has title append title
        if (item.querySelector(".uol-gallery__item__title")) {
          buttonText +=
            ": " + item.querySelector(".uol-gallery__item__title").innerHTML;
        }
        // If item does not have title and is type video append ": Video"
        else if (item.classList.includes("uol-gallery__item--video")) {
          buttonText += ": Video";
        }
        // If item does not have title and is type image append ": Image"
        else if (item.classList.includes("uol-gallery__item--image")) {
          buttonText += ": Image";
        }
    
        button.innerHTML = `<span class="hide-accessible">${buttonText}</span>`;
        if (index === 4 && items.length > 5) {
          button.classList.add("uol-gallery__button--with-count");
          button.innerHTML =
            button.innerHTML +
            `<span aria-hidden="true">+${items.length - (index + 1)}</span>`;
        } else {
          button.classList.add("uol-button");
          button.classList.add("uol-icon");
          button.classList.add("uol-icon--icon-only");
          button.classList.add("uol-gallery__button--open-item");
    
          // Check if 'uol-gallery__item--video-play-icon' exists on video item (this can be passed through config)
          const isVideoPlayIcon = item.classList.contains('uol-gallery__item--video-play-icon');
          isVideoPlayIcon ? button.classList.add("uol-icon--mdiPlay") : button.classList.add("uol-icon--mdiArrowExpand");
        }
        button.onclick = (event) => {
          openGalleryModal(items, index, event.target);
        };
        item.querySelector(".uol-gallery__image-container").appendChild(button);
      });
    };
    
    /*
     * youtube_parser
     * Get YouTube IDs from URLs
     */
    const youtube_parser = (url) => {
      var regExp =
        /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
      var match = url.match(regExp);
      return match && match[7].length == 11 ? match[7] : false;
    };
    
    /**
     * Get images for gallery items from YouTube
     * @param {NodeList} items
     */
    const galleryVideoImages = (items) => {
      // TODO: Add full oEmbed support
      items.forEach((item) => {
        const videoUrl = item.dataset.video;
    
        if (videoUrl) {
          const youtubeId = youtube_parser(videoUrl);
          const itemImg = item.querySelector("img");
          itemImg.hidden = true;
          item.setAttribute("aria-busy", true )
    
          itemImg.onload = () => {
            item.setAttribute("aria-busy", false);
            itemImg.hidden = false;
          };
    
          if (youtubeId) {
            itemImg.src =
              "https://i.ytimg.com/vi/" + youtubeId + "/maxresdefault.jpg";
    
            fetch(
              "https://www.youtube.com/oembed?url=http%3A//www.youtube.com/watch?v%3D" +
                youtubeId +
                "&format=json",
              {
                method: "get",
              }
            )
              .then((response) => response.json())
              .then((data) => {
                const youtubeSrc = data.html.match(
                  /\<iframe.+src\=(?:\"|\')(.+?)(?:\"|\')(?:.+?)\>/
                );
                const youtubeURL = youtubeSrc[1].replace(
                  "youtube.com",
                  "youtube-nocookie.com"
                );
    
                item.dataset.youtubeUrl =
                  youtubeURL + "&rel=0&enablejsapi=1";
                item.dataset.youtubeTitle = data.title;
                item.dataset.youtubeId = youtubeId;
              });
          }
        }
      });
    };
    
    /**
     * Creates the gallery modal on button click
     * @param {NodeList} items - NodeList of the gallery's initial items
     * @param {number} index - the index of the gallery item to open on
     * @param {Node} focusedElementBeforeModal - HTML node that triggered the gallery open
     */
    const openGalleryModal = (items, index, focusedElementBeforeModal) => {
    
      // Create modal
      const modal = modalOuter();
    
      // Add close button to modal
      modal.innerHTML += buttonClose;
    
      // Add gallery items to modal
      modal.innerHTML += galleryItems(items);
    
      // Add navigation to modal
      modal.innerHTML += galleryNavigation(items, index);
    
      // Add listeners
      addListeners(modal, focusedElementBeforeModal);
    
      // Add modal to page
      document.body.appendChild(modal);
    
      // Scroll to selected item
      const modelItems = modal.querySelectorAll(".uol-gallery-modal__item");
      // Delay scroll to work around iOS Safari rendering issues
      setTimeout(() => {
        modelItems[index].focus();
        modelItems[index].scrollIntoView();
        // Add smooth scroll class for future interactions
        const modalTrack = modal.querySelector(".uol-gallery-modal__track");
        modalTrack.classList.add("uol-gallery-modal__track--smooth");
      }, 10);
    
      // Resize iframes to keep aspect ratio
      const videoFigures = modal.querySelectorAll(
        ".uol-gallery-modal__figure--video"
      );
    
      videoFigures.forEach( videoFigure => {
        const container = videoFigure;
        const object = videoFigure.querySelector(
          ".uol-gallery-modal__image-container--video"
        );
        const aspectRatio = 16 / 9;
    
        function update() {
          const isTall =
            container.clientWidth / container.clientHeight < aspectRatio;
    
          // if IE 11 TODO: IE11 hack
          if (window.msCrypto) {
            object.style.width = "100%";
            object.style.height = "100%";
          } else {
            object.style.width = isTall ? "100%" : "auto";
            object.style.height = isTall ? "auto" : "100%";
          }
        }
    
        new ResizeObserver(update).observe(container);
      })
    
      // Hide other page content
      if (galleryPageContent) {
        galleryPageContent.hidden = true;
      }
    };
    
    
    export const uolGallery = () => {
      const galleries = document.querySelectorAll(".uol-gallery");
    
      galleries.forEach((gallery) => {
        const items = gallery.querySelectorAll(".uol-gallery__item");
    
        galleryVideoImages(items);
    
        galleryAddButtons(items);
      });
    };
    
  • URL: /components/raw/uol-gallery/gallery.module.js
  • Filesystem Path: src/library/02-components/gallery/gallery.module.js
  • Size: 24 KB
{
  "gallery": {
    "headingLevel": 2,
    "items": [
      {
        "title": "Ear tickle therapy, the world's thinnest gold and Hajj regulation",
        "type": "video",
        "video": "https://www.youtube.com/watch?v=PpilTVi5Yk4",
        "img": {
          "caption": "Ear tickle therapy, the world's thinnest gold and Hajj regulation"
        },
        "content": "<p>Lorem ipsum <a href='/some-text-link'>dolor sit amet consectetur</a> adipisicing elit. Corrupti, quasi nostrum blanditiis hic totam a id architecto molestias, sunt vitae iste consectetur cupiditate incidunt autem illo, consequuntur recusandae? Ipsam, asperiores.</p><p>Expedita saepe illo vero sit et! Eveniet, deserunt. Nihil omnis fugit ut veniam ullam, non maiores, consequatur amet enim dolore totam, laborum accusantium voluptatum iure est ab aspernatur reiciendis explicabo.</p><p>Eos reprehenderit suscipit, at eveniet, minus ea quod quis provident, nisi fugit maiores molestias culpa. Rerum rem pariatur quo mollitia autem omnis eum officiis, natus beatae eos saepe culpa earum!</p><p>Eum ut tempore delectus quos unde tenetur neque perspiciatis. Dicta sunt rem dolore, in ab impedit assumenda, quaerat neque quos veritatis consequatur accusantium dignissimos eius natus iusto nostrum eos maxime.</p>"
      },
      {
        "title": "Edible bugs, paying for parks, animals and climate change",
        "type": "video",
        "video": "https://www.youtube.com/watch?v=_-hZg6E6OOc",
        "img": {
          "caption": "Edible bugs, paying for parks, animals and climate change"
        },
        "content": "<p>Lorem ipsum <a href='/some-text-link'>dolor sit amet consectetur</a> adipisicing elit. Corrupti, quasi nostrum blanditiis hic totam a id architecto molestias, sunt vitae iste consectetur cupiditate incidunt autem illo, consequuntur recusandae? Ipsam, asperiores.</p><p>Expedita saepe illo vero sit et! Eveniet, deserunt. Nihil omnis fugit ut veniam ullam, non maiores, consequatur amet enim dolore totam, laborum accusantium voluptatum iure est ab aspernatur reiciendis explicabo.</p><p>Eos reprehenderit suscipit, at eveniet, minus ea quod quis provident, nisi fugit maiores molestias culpa. Rerum rem pariatur quo mollitia autem omnis eum officiis, natus beatae eos saepe culpa earum!</p><p>Eum ut tempore delectus quos unde tenetur neque perspiciatis. Dicta sunt rem dolore, in ab impedit assumenda, quaerat neque quos veritatis consequatur accusantium dignissimos eius natus iusto nostrum eos maxime.</p>"
      },
      {
        "title": "Studying in a different country",
        "type": "video",
        "video": "https://www.youtube.com/watch?v=j4pt8l7e3u8",
        "img": {
          "caption": "Studying in a different country"
        },
        "content": "<p>Being an international student can be nerve-wracking, but at the University of Leeds we're here to help every step of the way. If you're studying in a new country, there's lots to get your head around, but lots of opportunities too, to make new friends and try new experiences. </p><p>Every year more than 6,000 international students choose to study with us, making our campus one of the most diverse and multicultural in the UK. </p><p>Join our new Virtual Open Days: <a href='https://virtualopenday.leeds.ac.uk'>https://virtualopenday.leeds.ac.uk</a></p>"
      },
      {
        "title": "You Can Master with the University of Leeds",
        "type": "video",
        "video": "https://www.youtube.com/watch?v=Ej7oXq_AHCU",
        "img": {
          "caption": "You Can Master with the University of Leeds"
        },
        "content": "<p>Whether you're continuing from undergraduate study, returning to education or changing direction, a postgraduate degree from the University of Leeds can help you take the next step. We'll challenge you to explore, question and discover.</p><p>You can Master with the University of Leeds.</p><p>Start your journey to postgraduate study today: <a href='https://www.leeds.ac.uk/info/101000/masters_courses'>Masters Courses</a></p>"
      },
      {
        "title": "Understanding the past, shaping the present and innovating for the future with research at Leeds",
        "type": "video",
        "video": "https://youtu.be/_zbLlZ10R5Y",
        "img": {
          "caption": "Understanding the past, shaping the present and innovating for the future with research at Leeds"
        },
        "content": "<p>This month, we're deepening our understanding of the past, present and future through research.<p><p>Preserving invaluable cultural legacies for future generations</p><p>Children playing wallops (nine-pins) in the street at Castle Bolton in Wensleydale, North Yorkshire, 1964. A group of men can be seen sitting on a bench, watching men from the village playing quoits on the grass verge.</p><p>The University's Dialect and Heritage project has been awarded £530,500 from The National Lottery Heritage Fund to open up the extensive Leeds Archive of Vernacular Culture (LAVC) to the public. </p><p>As well as the National Lottery Heritage Fund £530,500 grant, the University of Leeds’ Footsteps Fund and other alumni donations have contributed almost £110,000 to the project, plus up to £23,000 from the partner museums.</p><p>The archive includes the ground-breaking work of the Survey of English Dialects, carried out by nine intrepid fieldworkers in the 1950s and is held in Leeds University Library’s Special Collections. The survey will now be updated and made available online for the first time.</p><p>Skipping breakfast linked to lower GCSE grades</p><p>Researchers, from the University of Leeds, have for the first time demonstrated a link between eating breakfast and GCSE performance for secondary school students in the UK.</p><p>Adding together all of a student’s exam results, they found that students who said they rarely ate breakfast achieved nearly two grades lower than those who rarely missed their morning meal.</p><p>The findings were covered extensively in the press, including on The Telegraph, The Mirror and the Daily Mail. </p><p>Pioneering new radiotherapy technologies</p><p>Leeds scientists and clinicians have been awarded a major cash boost from Cancer Research UK to pioneer new radiotherapy technologies that could help more people in Yorkshire survive cancer.</p><p>Professor David Sebag-Montefiore standing next to a radiotherapy machine in a blue room treatment room in a hospital in Leeds.</p><p>Experts from the University of Leeds and Leeds Teaching Hospitals NHS Trust are set to receive £3.5 million over the next five years to fund advances in radiotherapy research, including the use of artificial intelligence with imaging technology.</p>"
      },
      {
        "title": "New cancer research, heart attack care and superbug innovation",
        "type": "video",
        "video": "https://youtu.be/y28L6rzglNY",
        "img": {
          "caption": "New cancer research, heart attack care and superbug innovation"
        },
        "content": "<p>October roundup of some the latest research and education stories from the University.</p><p>Improving cancer treatments and patient care through collaboration</p><p>Researchers in Yorkshire are collaborating on a new national data hub that aims to transform how cancer data from across the UK can be used to improve patient care.</p><p>Collaborators from Yorkshire and Humber include the University of Leeds, Leeds Teaching Hospitals NHS Trust, University of Sheffield, Sheffield Teaching Hospitals, Sheffield Children’s Hospitals, Yorkshire and Humber Local Health Care Record, and Yorkshire & Humber Academic Health Science Network. </p><p>The work has been awarded £4.5 million to deliver DATA-CAN, the Health Data Research Hub for Cancer. </p><p>Another exciting Light Night in Leeds</p><p>Art and biology met on campus for another year of Light Night. </p><p>Light Night is the UK’s largest annual arts and light festival. It will see the transformation of some of Leeds' most recognisable indoor and outdoor spaces during two special nights.</p><p>Visitors were invited to step inside The Nectary - a series of huge, glowing flower heads, carefully crafted from recycled plastic and hung from trees in University Square - amongst other amazing cultural attractions on campus.</p><p>Leeds scientists reveal gender gaps in heart attack care </p><p>In partnership with the British Heart Foundation, Leeds researchers have helped to highlight the inequalities in awareness, diagnosis, treatment and care for women suffering a heart attack. </p><p>Professor Chris Gale's research looked at the disparity in care for men and women after experiencing a heart attack. The new report was covered widely, including extensively on the BBC.</p><p>Scientists have uncovered a novel approach to prevent and treating a widespread superbug</p><p>Helicobacter pylori, a bacterial pathogen carried by 4.4 billion people worldwide</p><p>Working with researchers across the UK and in Germany, Leeds scientists have developed an approach to tackling a widespread superbug using natural ingredients.</p><p>The bug, Helicobacter pylori (H. pylori), is a bacterial pathogen carried by 4.4 billion people worldwide, with the highest prevalence in Africa, Latin America and the Caribbean.</p><p>Although the majority of infections show no symptoms, if left untreated the pathogen can cause chronic inflammation of the stomach lining, ulcers and is associated with an increased risk of gastric cancer.</p><p>Current treatments involve multi-target therapy with a combination of antibiotics, but this has promoted the emergence of resistant strains.</p><p>Scientists have now uncovered a novel antibiotic-free approach using only food- and pharmaceutical-grade ingredients.</p>"
      }
    ]
  }
}