Altro - Galleria multimediale dei prodotti

Galleria multimediale dei prodotti

Esempio di combinazione di più elementi multimediali Images, Video, 3D Viewer in un'unica galleria di prodotti. Tutti gli elementi sono stati creati con le soluzioni Alter Product.

Documentazione Altro - Incorporamento della galleria multimediale dei prodotti - anteprima dal vivo / demo incorporata (Vanilla JS).
1<!DOCTYPE html>
2<html>
3  <head>
4    <title>Docs Advanced - Product media gallery</title>
5    <meta charset="utf-8" />
6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
8    <link rel="stylesheet" href="./src/Styles.css">
9    <link href="https://vjs.zencdn.net/8.16.1/video-js.css" rel="stylesheet" />
10  </head>
11
12  <body>
13    <div id="wrapper">
14      <div id="displayWrapper"></div>
15      <div id="buttonsWrapper"></div>
16    </div>
17
18    <script src="./src/App.js"></script>
19    <script src="https://vjs.zencdn.net/8.16.1/video.js"></script>
20  </body>
21</html>
Vanilla JS - App.js
const responseApi = [
  {
    type: 'img',
    urlThumb: './assets/tshirt/black/image_0_thumb.png',
    urlContent: './assets/tshirt/black/image_0_big.png',
    alt: 'T-Shirt front',
  },
  {
    type: 'img',
    urlThumb: './assets/tshirt/black/image_1_thumb.png',
    urlContent: './assets/tshirt/black/image_1_big.png',
    alt: 'T-Shirt back',
  },
  {
    type: 'video',
    urlThumb: './assets/tshirt/black/image_2_thumb.png',
    urlContent: './assets/tshirt/black/image_0_big.png',
    alt: 'T-Shirt video thumbnail',
  },
  {
    type: 'viewer3D',
    urlThumb: './assets/tshirt/black/image_3_thumb.png',
    urlContent: './assets/tshirt/black/image_0_big.png',
    alt: 'T-Shirt 3D Viewer thumbnail',
  },
];

let selectedPreview = 0;

function initialiseProduct() {
  const buttonsWrapper = document.getElementById('buttonsWrapper');
  if (!buttonsWrapper) return;

  responseApi.forEach((item, index) => {
    const elementContainer = document.createElement('div');
    elementContainer.className = 'buttonContainer';

    if (index === selectedPreview) {
      displayMedia(index);
      elementContainer.classList.add('active');
    }

    buttonsWrapper.appendChild(elementContainer);

    elementContainer.addEventListener('click', function () {
      buttonClickHandler(this, index);
    });

    const elementThumb = document.createElement('img');
    elementThumb.className = 'imgThumb';
    elementThumb.src = item.urlThumb;
    elementThumb.alt = item.alt;
    elementContainer.appendChild(elementThumb);

    if (item.type === 'video') {
      const elementIcon = document.createElement('img');
      elementIcon.className = 'button-icon';
      elementIcon.src = './assets/icons/icon-play-video.svg';
      elementIcon.alt = 'Play video';
      elementContainer.appendChild(elementIcon);
    } else if (item.type === 'viewer3D') {
      const elementIcon = document.createElement('img');
      elementIcon.className = 'button-icon';
      elementIcon.src = './assets/icons/icon-3d.svg';
      elementIcon.alt = 'Open 3D Viewer';
      elementContainer.appendChild(elementIcon);
    }
  });
}

function buttonClickHandler(element, index) {
  if (selectedPreview === index) return;
  selectedPreview = index;

  document.querySelectorAll('#buttonsWrapper .buttonContainer').forEach((el) => {
    el.classList.remove('active');
  });

  element.classList.add('active');
  displayMedia(index);
}

function adjustVideoSize(player, container) {
  function updateSize() {
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;
    const containerRatio = containerWidth / containerHeight;

    const videoWidth = player.videoWidth();
    const videoHeight = player.videoHeight();
    const videoRatio = videoWidth / videoHeight;

    if (videoRatio > containerRatio) {
      player.width(containerWidth);
      player.height(containerWidth / videoRatio);
    } else {
      player.height(containerHeight);
      player.width(containerHeight * videoRatio);
    }
  }

  player.on('loadedmetadata', updateSize);
  window.addEventListener('resize', updateSize);
}

function displayMedia(index) {
  const displayWrapper = document.getElementById('displayWrapper');
  if (!displayWrapper) return;

  const { type, urlContent, alt } = responseApi[index];

  if (type === 'img') {
    const img = document.createElement('img');
    img.src = urlContent;
    img.alt = alt || '';
    displayWrapper.replaceChildren(img);
    return;
  }

  if (type === 'video') {
    const video = document.createElement('video');
    video.id = 'my-video';
    video.className = 'video-js';
    video.loop = true;
    video.autoplay = true;
    video.muted = true;
    video.preload = 'auto';
    video.setAttribute('playsinline', '');

    const source = document.createElement('source');
    source.src = './assets/tshirt/black/product-media-gallery-loop-embeding.webm';
    source.type = 'video/webm';

    const noJsMessage = document.createElement('p');
    noJsMessage.className = 'vjs-no-js';
    noJsMessage.textContent =
      'To view this video please enable JavaScript and use a browser that supports HTML5 video.';

    video.appendChild(source);
    video.appendChild(noJsMessage);

    displayWrapper.replaceChildren(video);

    if (window.videojs) {
      const player = window.videojs(video);
      adjustVideoSize(player, displayWrapper);
    } else {
      console.warn('Video.js is not loaded yet!');
    }
    return;
  }

  if (type === 'viewer3D') {
    const iframe = document.createElement('iframe');
    iframe.loading = 'lazy';
    iframe.title = 'Product Viewer';
    iframe.src = 'https://alterproduct.com/app/viewer/1';
    iframe.width = '100%';
    iframe.height = '100%';
    iframe.frameBorder = '0';
    iframe.allowFullscreen = true;

    // hardening (opcjonalnie): iframe sandbox też możesz dodać, jeśli to embed zewnętrzny
    // iframe.sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups';

    displayWrapper.replaceChildren(iframe);
  }
}

initialiseProduct();
Vanilla JS - Styles.css
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  width: 100%;
  height: 100%;
  display: grid;
  justify-content: center;
  align-items: center;
}

#wrapper {
  display: grid;
  min-height: 400px;
  grid-template-rows: minmax(340px, 50vh) auto;
  grid-template-columns: 1fr;
  gap: 20px;
  user-select: none;
  margin: 10px;
  max-width: 700px;
}

#displayWrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
}

@media only screen and (max-width: 720px) {
  #displayWrapper {
    width: calc(100vw - 40px);
  }
}

#displayWrapper img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

#buttonsWrapper {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 1fr;
}

@media only screen and (max-width: 600px) {
  #buttonsWrapper {
    grid-template-columns: repeat(3, 1fr);
  }
  #wrapper {
    gap: 10px;
  }
}

@media only screen and (max-width: 450px) {
  #buttonsWrapper {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media only screen and (max-width: 300px) {
  #buttonsWrapper {
    grid-template-columns: repeat(1, 150px);
    grid-auto-rows: 150px;
    justify-content: center;
  }
}

.buttonContainer {
  cursor: pointer;
  transition: 0.3s;
  position: relative;
  overflow: hidden;
}

.buttonContainer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: 0.3s;
}

.button-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 120px !important;
  height: 120px !important;
  object-fit: contain;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.5));
}

.buttonContainer:hover .imgThumb {
  transform: scale(1.2);
}

.active {
  cursor: auto;
  box-shadow: inset 0 0 0 1px rgb(129, 129, 129);
}

.active:hover .imgThumb {
  transform: scale(1);
}

/* Video */
video,
.video-js {
  background: white !important;
  outline: none;
  border: none;
}

.vjs-poster {
  background: transparent !important;
}

video:focus {
  outline: none;
}
React - AdvancedProductMediaGallery.js
import React, { useState } from 'react';
import './Styles.css';
import { responseApi } from './responseApi';

import MediaButtons from './MediaButtons';
import DisplayMedia from './DisplayMedia';

const AdvancedProductMediaGallery = () => {
  const [selectedPreview, setSelectedPreview] = useState(0);

  return (
    <div id="wrapper">
      <DisplayMedia responseApi={responseApi} selectedPreview={selectedPreview} />
      <MediaButtons
        responseApi={responseApi}
        selectedPreview={selectedPreview}
        setSelectedPreview={setSelectedPreview}
      />
    </div>
  );
};

export default AdvancedProductMediaGallery;
React - DisplayMedia.js
import React from 'react';
import VideoComponent from './VideoComponent';

const DisplayMedia = ({ responseApi, selectedPreview }) => {
  const item = responseApi[selectedPreview];

  return (
    <div id="displayWrapper">
      {item.type === 'img' ? (
        <img src={item.urlContent} alt={item.alt} />
      ) : null}

      {item.type === 'video' ? (
        <VideoComponent />
      ) : null}

      {item.type === 'viewer3D' ? (
        <iframe
          loading="lazy"
          title="Product Viewer"
          src="https://alterproduct.com/app/viewer/1"
          width="100%"
          height="100%"
          frameBorder="0"
          allowFullScreen
        />
      ) : null}
    </div>
  );
};

export default DisplayMedia;
React - MediaButtons.js
const MediaButtons = ({ responseApi, selectedPreview, setSelectedPreview }) => {
  return (
    <div id="buttonsWrapper">
      {responseApi.map((item, index) => (
        <button
          key={'buttonMedia' + index}
          type="button"
          className={'buttonContainer ' + (selectedPreview === index ? 'active' : '')}
          onClick={() => setSelectedPreview(index)}
        >
          <img src={item.urlThumb} alt={item.alt} className="imgThumb" />

          {item.type === 'video' ? (
            <img src={'/assets/icons/icon-play-video.svg'} alt={'Play video'} className="button-icon" />
          ) : null}

          {item.type === 'viewer3D' ? (
            <img src={'/assets/icons/icon-3d.svg'} alt={'Open 3D Viewer'} className="button-icon" />
          ) : null}
        </button>
      ))}
    </div>
  );
};

export default MediaButtons;
React - responseApi.js
export const responseApi = [
  {
    type: 'img',
    urlThumb: '/assets/image/tshirt/black/image_0_thumb.png',
    urlContent: '/assets/image/tshirt/black/image_0_big.png',
    alt: 'T-Shirt front',
  },
  {
    type: 'img',
    urlThumb: '/assets/image/tshirt/black/image_1_thumb.png',
    urlContent: '/assets/image/tshirt/black/image_1_big.png',
    alt: 'T-Shirt back',
  },
  {
    type: 'video',
    urlThumb: '/assets/image/tshirt/black/image_2_thumb.png',
    urlContent: '/assets/image/tshirt/black/image_0_big.png',
    alt: 'T-Shirt video thumbnail',
  },
  {
    type: 'viewer3D',
    urlThumb: '/assets/image/tshirt/black/image_3_thumb.png',
    urlContent: '/assets/image/tshirt/black/image_0_big.png',
    alt: 'T-Shirt 3D Viewer thumbnail',
  },
];
React - Styles.css
/* reuse Styles.css from vanilla example */
Apri il progetto su GitHubLogo di GitHub