Copy environment

Detail Hero

<div class="detail-hero">
    <div class="detail-hero__inner">
        <div class="detail-hero__item">
            <div class="detail-hero__item-inner">
                <figure class="image image--fluid detail-hero__image">
                    <img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201400%20700%22%3E%3C%2Fsvg%3E" data-srcset="//via.placeholder.com/1400x700 1400w" data-sizes="auto" alt="" class="image__img lazyload">

                </figure>
            </div>
        </div>
        <div class="detail-hero__item">
            <div class="detail-hero__item-inner">
                <figure class="image image--fluid detail-hero__image">
                    <img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201400%20700%22%3E%3C%2Fsvg%3E" data-srcset="//via.placeholder.com/1400x700 1400w" data-sizes="auto" alt="" class="image__img lazyload">

                </figure>
            </div>
        </div>
        <div class="detail-hero__item">
            <div class="detail-hero__item-inner">
                <figure class="image image--fluid detail-hero__image">
                    <img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201400%20700%22%3E%3C%2Fsvg%3E" data-srcset="//via.placeholder.com/1400x700 1400w" data-sizes="auto" alt="" class="image__img lazyload">

                </figure>
            </div>
        </div>
        <div class="detail-hero__item">
            <div class="detail-hero__item-inner">
                <figure class="image image--fluid detail-hero__image">
                    <img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201400%20700%22%3E%3C%2Fsvg%3E" data-srcset="//via.placeholder.com/1400x700 1400w" data-sizes="auto" alt="" class="image__img lazyload">

                </figure>
            </div>
        </div>
    </div>
    <div class="detail-hero__left-button"></div>
    <div class="detail-hero__right-button"></div>
    <div class="detail-hero__bubble-list">
        <div class="detail-hero__bubble-item" data-slide=0>
            <div class="detail-hero__bubble"></div>
        </div>
        <div class="detail-hero__bubble-item" data-slide=1>
            <div class="detail-hero__bubble"></div>
        </div>
        <div class="detail-hero__bubble-item" data-slide=2>
            <div class="detail-hero__bubble"></div>
        </div>
        <div class="detail-hero__bubble-item" data-slide=3>
            <div class="detail-hero__bubble"></div>
        </div>
    </div>
</div>
<div class="detail-hero">
    <div class="detail-hero__inner">
        {% for item in data.items %}
            <div class="detail-hero__item">
                <div class="detail-hero__item-inner">
                    {% include '@image' with {class: "detail-hero__image", modifier: "image--fluid", data: item.image|srcset('1400x700')} %}
                </div>
            </div>
        {% endfor %}
    </div>
    {% if data.items|length > 1 %}
        <div class="detail-hero__left-button"></div>
        <div class="detail-hero__right-button"></div>
        <div class="detail-hero__bubble-list">
            {% for item in data.items %}
                <div class="detail-hero__bubble-item" data-slide={{loop.index - 1}}>
                    <div class="detail-hero__bubble"></div>
                </div>
            {% endfor %}
        </div>
    {% endif %}
</div>
{
  "language": "en-US",
  "data": {
    "items": [
      {
        "image": true,
        "link": "#"
      },
      {
        "image": true,
        "link": "#"
      },
      {
        "image": true,
        "link": "#"
      },
      {
        "image": true,
        "link": "#"
      }
    ]
  }
}
  • Content:
    @keyframes detail-hero__item--appear {
        from {
            transform: translateX(100%);
        }
    
        to {
            transform: translateX(0);
        }
    }
    
    @keyframes detail-hero__item--disappear {
        from {
            transform: translateX(0);
        }
    
        to {
            transform: translateX(100%);
        }
    }
    
    @keyframes detail-hero__item-inner--lighten {
        from {
            filter: brightness(80%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        }
    
        to {
            filter: brightness(100%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        }
    }
    
    @keyframes detail-hero__item-inner--darken {
        from {
            filter: brightness(100%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        }
    
        to {
            filter: brightness(80%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        }
    }
    
    @keyframes detail-hero__image--appear {
        from {
            transform: translateX(-80%);
        }
    
        to {
            transform: translateX(0);
        }
    }
    
    @keyframes detail-hero__image--disappear {
        from {
            transform: translateX(0);
        }
    
        to {
            transform: translateX(-80%);
        }
    }
    
    .detail-hero {
        width: 100%;
        transition: border-bottom-color $transition-duration $transition-easing;
        @include aspect-ratio(100, 50, detail-hero__inner);
    }
    
    .detail-hero__inner {
        overflow: hidden;
        position: relative;
    }
    
    .detail-hero__left-button {
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        width: 30%;
        z-index: map_get($zindex, detailHeroGradient);
        opacity: 0;
        transition: opacity $transition-duration $transition-easing;
        background: linear-gradient(.25turn, var(--color-compliment), transparent); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        cursor: pointer;
    
        &:hover {
            @media (hover: hover) {
                opacity: .5;
            }
        }
    }
    
    .detail-hero__right-button {
        position: absolute;
        right: 0;
        top: 0;
        bottom: 0;
        width: 30%;
        z-index: map_get($zindex, detailHeroGradient);
        opacity: 0;
        transition: opacity $transition-duration $transition-easing;
        background: linear-gradient(.25turn, transparent, var(--color-compliment)); /* stylelint-disable-line plugin/no-unsupported-browser-features */
        cursor: pointer;
    
        &:hover {
            @media (hover: hover) {
                opacity: .5;
            }
        }
    }
    
    .detail-hero__item {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
    
        &.is-hidden {
            z-index: map_get($zindex, detailHeroBelow);
    
            &.is-reversed {
                animation: detail-hero__item--disappear 1s $transition-easing forwards;
                z-index: map_get($zindex, detailHeroAbove);
            }
        }
    
        &.is-active {
            animation: detail-hero__item--appear 1s $transition-easing forwards;
            z-index: map_get($zindex, detailHeroAbove);
    
            &.is-reversed {
                animation: none;
                z-index: map_get($zindex, detailHeroBelow);
            }
        }
    }
    
    .detail-hero__item-inner {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
    
        .is-hidden & {
            animation: detail-hero__item-inner--darken 1s $transition-easing forwards;
        }
    
        .is-active & {
            animation: none;
        }
    
        .is-hidden.is-reversed & {
            animation: none;
        }
    
        .is-active.is-reversed & {
            animation: detail-hero__item-inner--lighten 1s $transition-easing forwards;
        }
    }
    
    .detail-hero__image {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        overflow: hidden;
    
        .is-active & {
            animation: detail-hero__image--appear 1s $transition-easing forwards;
        }
    
        .is-active.is-reversed & {
            animation: none;
        }
    
        .is-hidden.is-reversed & {
            animation: detail-hero__image--disappear 1s $transition-easing forwards;
        }
    }
    
    .image__img {
        .detail-hero & {
            width: 100%;
            display: block;
        }
    }
    
    .detail-hero__bubble-list {
        position: absolute;
        justify-content: center;
        right: 16px;
        bottom: 8px;
        display: flex;
        z-index: map_get($zindex, detailHeroBubbles);
    
        @include bp(sm-min) {
            right: 32px;
            bottom: 7px;
        }
    }
    
    .detail-hero__bubble-item {
        width: 10px;
        height: 10px;
        cursor: pointer;
    
        @include bp(sm-min) {
            width: 24px;
            height: 24px;
        }
    }
    
    .detail-hero__bubble {
        border-radius: 50%;
        margin: 2px;
        width: 6px;
        height: 6px;
        transition: $transition-duration $transition-easing;
        transition-property: transform, border, background-color;
        border: 1px solid var(--color-brand);
    
        @include bp(sm-min) {
            border: 8px solid var(--color-brand);
            transform: scale(.25);
            margin: 3px;
            width: 18px;
            height: 18px;
        }
    
        &.is-active {
            background-color: var(--color-brand);
    
            @include bp(sm-min) {
                background-color: transparent;
                border: 2px solid var(--color-brand);
                transform: scale(1);
            }
        }
    }
    
  • URL: /components/raw/detail-hero/detail-hero.scss
  • Filesystem Path: src/patterns/components/detail-hero/detail-hero.scss
  • Size: 5.1 KB
  • Content:
    import './detail-hero.scss';
    import Component from '@component';
    import ClickEvent = JQuery.ClickEvent;
    import TouchStartEvent = JQuery.TouchStartEvent;
    import TouchMoveEvent = JQuery.TouchMoveEvent;
    
    interface IDetailHeroSettings {
        leftButtonClass: string;
        rightButtonClass: string;
        bubbleItemClass: string;
        bubbleClass: string;
        imageClass: string;
        isHiddenClass: string;
        isActiveClass: string;
        isReversedClass: string;
        isHoveringLeftClass: string;
        isHoveringRightClass: string;
        clickThreshold: number;
        swipeThreshold: number;
    }
    
    export default class DetailHero extends Component {
        static initSelector: string = '.detail-hero';
    
        images: JQuery;
        bubbles: JQuery;
        bubbleItems: JQuery;
        activeImage: number;
        settings: IDetailHeroSettings;
        readyForInteraction: boolean;
        touchStartX: number;
        leftButton: JQuery;
        rightButton: JQuery;
    
        constructor(element: HTMLElement) {
            super(element);
    
            this.settings = {
                bubbleClass: 'detail-hero__bubble',
                bubbleItemClass: 'detail-hero__bubble-item',
                clickThreshold: .3,
                imageClass: 'detail-hero__item',
                isActiveClass: 'is-active',
                isHiddenClass: 'is-hidden',
                isHoveringLeftClass: 'is-hovering-left',
                isHoveringRightClass: 'is-hovering-right',
                isReversedClass: 'is-reversed',
                leftButtonClass: 'detail-hero__left-button',
                rightButtonClass: 'detail-hero__right-button',
                swipeThreshold: 150,
            };
    
            this.images = this.element.find('.' + this.settings.imageClass);
    
            if (this.images.length > 1) {
                this.bubbles = this.element.find('.' + this.settings.bubbleClass);
                this.bubbleItems = this.element.find('.' + this.settings.bubbleItemClass);
    
                this.activeImage = 0;
                this.bubbles[this.activeImage].classList.add(this.settings.isActiveClass);
                this.readyForInteraction = true;
    
                this.leftButton = this.element.find('.' + this.settings.leftButtonClass);
                this.rightButton = this.element.find('.' + this.settings.rightButtonClass);
    
                this.images.css('display', 'none');
                this.images[this.activeImage].style.display = 'block';
    
                this.init();
            }
        }
    
        init(): void {
            this.bubbleItems.on('click', this.bubbleClickHandler.bind(this));
            this.leftButton.on('click', () => this.setActiveImage(this.activeImage - 1, true));
            this.rightButton.on('click', () => this.setActiveImage(this.activeImage + 1, false));
            this.swipeEvents();
        }
    
        bubbleClickHandler(event: ClickEvent): void {
            this.setActiveImage(event.currentTarget.attributes['data-slide'].value, false);
        }
    
        swipeEvents(): void {
            this.element.on('touchstart', this.touchStartHandler.bind(this));
            this.element.on('touchmove', this.touchMoveHandler.bind(this));
        }
    
        touchStartHandler(event: TouchStartEvent): void {
            this.touchStartX = event.touches[0].screenX;
        }
    
        touchMoveHandler(event: TouchMoveEvent): void {
            const currentTouchX: number = event.touches[0].screenX;
    
            if (this.touchStartX && Math.abs(currentTouchX - this.touchStartX) > this.settings.swipeThreshold) {
                const reverse: boolean = currentTouchX > this.touchStartX;
    
                this.setActiveImage(this.activeImage + (reverse ? -1 : 1), reverse);
                this.touchStartX = undefined;
            }
        }
    
        setActiveImage(index: number, reverse: boolean): void {
            if (this.readyForInteraction) {
                const animationEndListener: () => void = (): void => {
                    this.readyForInteraction = true;
                    this.images[newActive].removeEventListener('animationend', animationEndListener);
                };
    
                const previousActive: number = this.activeImage;
                const newActive: number = index === -1 ? this.images.length-1 : index % this.images.length;
    
                this.images.css('display', '');
                this.activeImage = newActive;
                this.readyForInteraction = false;
                this.images[newActive].addEventListener('animationend', animationEndListener);
    
                if (reverse) {
                    this.images.addClass(this.settings.isReversedClass);
                } else {
                    this.images.removeClass(this.settings.isReversedClass);
                }
    
                if (this.images.length > 1) {
                    this.images.removeClass(this.settings.isHiddenClass);
                    this.bubbles[previousActive].classList.remove(this.settings.isActiveClass);
                    this.images[previousActive].classList.remove(this.settings.isActiveClass);
                    this.images[previousActive].classList.add(this.settings.isHiddenClass);
                    this.images[newActive].classList.add(this.settings.isActiveClass);
                    this.bubbles[newActive].classList.add(this.settings.isActiveClass);
                }
            }
        }
    }
    
  • URL: /components/raw/detail-hero/detail-hero.ts
  • Filesystem Path: src/patterns/components/detail-hero/detail-hero.ts
  • Size: 5.1 KB
  • Handle: @detail-hero--default
  • Filesystem Path: src/patterns/components/detail-hero/detail-hero.twig
  • References (1): @image
  • Referenced by (1): @view-detail