<section class="supt-section-products-innovation">
<div class="supt-section-products-innovation__inner">
<div class="container supt-section-products-innovation__products">
</div>
</div>
</section>
No notes defined.
<section class="supt-section-products-innovation" {{ attrs|attrs }}>
<div class="supt-section-products-innovation__inner">
<div class="container supt-section-products-innovation__products">
{% for product in products %}
<div class="supt-section-products-innovation__product{{ product.isWifiVision ? ' -is-wifi-vision' : '' }}">
<div class="row justify-content-center align-items-center supt-section-products-innovation__product__inner">
<div class="col-12 col-md-4 col-lg-3">
<h2 class="supt-section-products-innovation__title">
{{ product.title }}
</h2>
</div>
<div class="col-12 col-md-4 col-lg-3 order-1 order-md-0">
<div class="supt-section-products-innovation__image-wrapper">
{% if product.isWifiVision %}
{# Wifi Vision animation #}
<span class="supt-section-products-innovation__image__zoom">
{% for i in 1..4 %}
<svg width="142" height="483" viewbox="0 0 142 483" fill="none" xmlns="http://www.w3.org/2000/svg" class="supt-section-products-innovation__image__zoom__radar">
<path d="M130 75.5C130 108.361 103.361 135 70.5 135C37.6391 135 12 108.361 12 75.5C12 44.6391 38.6391 16 70.5 16C103.361 16 130 42.6391 130 75.5Z" stroke="#ED002F" stroke-width="0.5" vector-effect="non-scaling-stroke"/>
</svg>
{% endfor %}
<img src="/sites/gv/files/flmngr/superhuit/innovation/overview/WifiVision-circle.png" alt="" class="supt-section-products-innovation__image__zoom__default">
</span>
{% endif %}
{% if product.image %}
<img src="{{ product.image.src }}" alt="{{ product.title }}" class="supt-section-products-innovation__image" style="--supt-innovation-product-desktop-width: {{ product.image.width.desktop }}px; --supt-innovation-product-tablet-width: {{ product.image.width.tablet }}px; --supt-innovation-product-mobile-width: {{ product.image.width.mobile }}px;">
{% endif %}
</div>
</div>
<div class="col-12 col-md-4 col-lg-3">
<p class="supt-section-products-innovation__description">
{{ product.description }}
</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
/* No context defined. */
import { animate, createTimeline, onScroll, stagger } from 'animejs';
import { Sizes } from '@/js/Sizes';
export class SectionProductsInnovation {
$element: HTMLElement;
$inner: HTMLElement;
products: Product[];
sizes: Sizes;
constructor($element: HTMLElement) {
this.$element = $element;
this.$inner = $element.querySelector('.supt-section-products-innovation__inner') as HTMLElement;
const $products = $element.querySelectorAll('.supt-section-products-innovation__product');
this.products = Array.from($products).map($product => new Product($product as HTMLElement));
this.sizes = new Sizes();
this.handleWindowResize(); // Init
this.handleScrollAnimation();
window.addEventListener('resize', this.handleWindowResize.bind(this));
}
getDeltaBetween3rdAnd4thProductImage() {
const $3rdProductImage = this.products[2].$imageWrapper;
const $4thProductImage = this.products[3].$imageWrapper;
const delta =
$4thProductImage.parentElement!.getBoundingClientRect().top -
$3rdProductImage.parentElement!.getBoundingClientRect().top;
this.products[3].$imageWrapper.style.top = `${-1 * delta}px`;
}
handleScrollAnimation() {
const DISPLAY_IMAGE_TRANSLATION: Record<string, string[]> = this.sizes.isDesktop
? { y: ['75vh', '0vh'] }
: { x: ['75vw', '0vw'] };
const HIDE_IMAGE_TRANSLATION: Record<string, string[]> = this.sizes.isDesktop
? { y: ['0vh', '-75vh'] }
: { x: ['0vw', '-75vw'] };
/**
* 1st product animation
*/
// Display 1st product image
animate(this.products[0].$image, {
scale: [0.5, 1],
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'center top',
leave: 'bottom top+=100vh',
sync: true,
}),
});
// Display 1st product text
animate([this.products[0].$title, this.products[0].$description], {
opacity: [0, 1],
delay: stagger(50),
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=100vh',
leave: 'bottom top+=120vh',
sync: true,
}),
});
/**
* 2nd product animation
*/
// Display 2nd product image (+ hide 1st product image)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=120vh',
leave: 'bottom top+=175vh',
sync: true,
}),
})
// Hide image of 1st product (needs to be the wrapper as it's not possible to re-animate the image because of animejs being dummy....!!!)
.add(this.products[0].$imageWrapper, {
...HIDE_IMAGE_TRANSLATION,
scale: [1, 0.5],
})
// Display image of 2nd product
.add(
this.products[1].$image,
{
...DISPLAY_IMAGE_TRANSLATION,
scale: [0.5, 1],
},
'<<'
);
// Display 2nd product text (+ hide 1st product text)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=130vh',
leave: 'bottom top+=160vh',
sync: true,
}),
})
// Hide inner of first product
.add(this.products[0].$inner, {
opacity: [1, 0],
})
// Display text of second product
.add([this.products[1].$title, this.products[1].$description], {
opacity: [0, 1],
delay: stagger(50),
});
/**
* 3rd product animation
*/
// Display 3rd product image (+ hide 2nd product image)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=190vh',
leave: 'bottom top+=255vh',
sync: true,
}),
})
// Hide image of 2nd product (needs to be the wrapper as it's not possible to re-animate the image because of animejs being dummy....!!!)
.add(this.products[1].$imageWrapper, {
...HIDE_IMAGE_TRANSLATION,
scale: [1, 0.5],
})
// Display image of 3rd product
.add(
this.products[2].$image,
{
...DISPLAY_IMAGE_TRANSLATION,
scale: [0.5, 1],
},
'<<'
);
// Display 3rd product text (+ hide 2nd product text)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=210vh',
leave: 'bottom top+=240vh',
sync: true,
}),
})
// Hide inner of first product
.add(this.products[1].$inner, {
opacity: [1, 0],
})
// Display text of second product
.add([this.products[2].$title, this.products[2].$description], {
opacity: [0, 1],
delay: stagger(50),
});
/**
* 4th product animation
*/
// Display 4th product image (+ hide 3rd product image)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=300vh',
leave: 'bottom bottom',
sync: true,
}),
})
// Display image of 3rd product
.add(this.products[3].$imageWrapper, {
opacity: [0, 1],
duration: 500,
})
// Hide image of 2nd product (needs to be the wrapper as it's not possible to re-animate the image because of animejs being dummy....!!!)
.add(
this.products[2].$imageWrapper,
{
opacity: [1, 0],
},
'+=300'
)
// Move the image of the fourth product to the middle of the screen
.add(
this.products[3].$imageWrapper,
{
transform: 'translate(0%, 35%)',
},
'<'
);
// Display 4th product text (+ hide 3rd product text)
createTimeline({
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom top+=330vh',
leave: 'bottom top+=360vh',
sync: true,
}),
})
// Hide inner of first product
.add(this.products[2].$inner, {
opacity: [1, 0],
})
// Display text of second product
.add([this.products[3].$title, this.products[3].$description], {
opacity: [0, 1],
delay: stagger(50),
});
// Animate the wifi vision animation
animate(this.products[3].$zoomRadars!, {
opacity: [1, 0],
scale: [1, 6],
duration: 2000,
loop: true,
ease: 'easeInOutSine',
delay: stagger(400, { ease: 'inQuad' }),
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom bottom-=100vh',
sync: 'play play pause pause',
}),
});
// Hide section
animate(this.$inner, {
opacity: [1, 0],
autoplay: onScroll({
container: document.body,
target: this.$element,
enter: 'bottom bottom',
leave: 'center bottom',
sync: true,
}),
});
}
handleWindowResize() {
this.getDeltaBetween3rdAnd4thProductImage();
}
}
class Product {
$element: HTMLElement;
$inner: HTMLElement;
$imageWrapper: HTMLElement;
$image: HTMLElement;
$title: HTMLElement;
$description: HTMLElement;
$zoom?: HTMLElement;
$zoomRadars?: NodeListOf<HTMLElement>;
constructor($element: HTMLElement) {
this.$element = $element;
this.$inner = $element.querySelector(
'.supt-section-products-innovation__product__inner'
) as HTMLElement;
this.$imageWrapper = $element.querySelector(
'.supt-section-products-innovation__image-wrapper'
) as HTMLElement;
this.$image = $element.querySelector('.supt-section-products-innovation__image') as HTMLElement;
this.$title = $element.querySelector('.supt-section-products-innovation__title') as HTMLElement;
this.$description = $element.querySelector(
'.supt-section-products-innovation__description'
) as HTMLElement;
// Zoom for Wifi Vision
this.$zoom = $element.querySelector(
'.supt-section-products-innovation__image__zoom'
) as HTMLElement;
this.$zoomRadars = $element.querySelectorAll(
'.supt-section-products-innovation__image__zoom__radar'
) as NodeListOf<HTMLElement>;
}
}
.supt-section-products-innovation {
margin-top: -50vh;
height: 400vh;
&__inner {
height: 100vh;
position: sticky;
top: 0;
}
&__products {
height: 100vh;
position: sticky;
top: 0;
}
&__product {
position: relative;
&.-is-wifi-vision {
.supt-section-products-innovation__image-wrapper {
position: relative;
}
.supt-section-products-innovation__image {
opacity: 0;
}
}
&__inner {
position: absolute;
inset: 0;
height: 100vh;
padding: $spacing-6 0;
row-gap: $spacing-6;
overflow: hidden;
flex-flow: column nowrap;
@media (min-width: $breakpoint-xs) {
padding: $spacing-12 0;
}
@media (min-width: $breakpoint-sm) {
padding: $spacing-24 0;
}
@media (min-width: $breakpoint-md) {
height: 100vh;
flex-flow: row wrap;
}
.col-12 {
flex: none;
/* @media (min-width: $breakpoint-md) {
flex: 0 0 100%;
} */
}
}
}
&__title,
&__description {
color: $color-white;
opacity: 0;
}
&__title {
@extend %t-h2;
text-shadow: $text-shadow-glow;
margin-bottom: 0;
}
&__image {
display: block;
height: auto;
max-width: 100%;
max-height: 100%;
object-fit: contain;
margin: auto;
width: var(--supt-innovation-product-mobile-width);
@media (min-width: $breakpoint-md) {
width: var(--supt-innovation-product-tablet-width);
}
@media (min-width: $breakpoint-lg) {
width: var(--supt-innovation-product-desktop-width);
}
/* For Wifi Vision animation */
&__zoom {
position: relative;
display: block;
position: absolute;
inset: 0;
&__radar {
display: block;
position: absolute;
inset: 0;
height: 100%;
margin: auto;
width: 80px;
@media (min-width: $breakpoint-md) {
width: 110px;
}
@media (min-width: $breakpoint-lg) {
width: 142px;
}
transform-origin: 50% 15%;
z-index: 0;
}
&__default {
max-width: 150%;
max-height: 110%;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: 1;
}
}
}
&__description {
@extend %paragraph-section-text;
}
}