Vue 360° Produit Tailwind CSS Interactive

Extraits & Composants HTML 08/04/2026 22:00:00 angularforall.com
Tailwind Css Vue 360 Produit Interactive Rotation E Commerce Template Html Css Js

Visionneuse produit 360° Tailwind CSS : rotation au drag, navigation clavier, miniatures de frames et expérience immersive pour e-commerce moderne.

<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
  <meta charset="UTF-8" />
  <meta name="copyright" content="AngularForAll" />
  <meta name="author" content="AngularForAll" />
  <meta name="robots" content="noindex, nofollow" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="Cache-Control" content="public, max-age=604800" />
  <title>Snippets View360 Product Tailwindcss 2026 05020020 | AngularForAll</title>
<!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>
  
  <!-- Configuration Tailwind personnalisée -->
  <script>
    tailwind.config = {
      darkMode: 'class',
      theme: {
        extend: {
          colors: {
            night: '#0a0a0f',
            eclipse: '#12121a',
            neon: {
              cyan: '#00f0ff',
              purple: '#b347ea',
              pink: '#ff3cac',
            },
            glass: 'rgba(255, 255, 255, 0.04)',
          },
          fontFamily: {
            display: ['"Space Grotesk"', 'system-ui', 'sans-serif'],
            mono: ['"JetBrains Mono"', 'monospace'],
          },
          borderRadius: {
            '4xl': '2.5rem',
          },
          boxShadow: {
            'neon-cyan': '0 0 30px rgba(0, 240, 255, 0.15), 0 0 60px rgba(0, 240, 255, 0.05)',
            'neon-purple': '0 0 30px rgba(179, 71, 234, 0.15), 0 0 60px rgba(179, 71, 234, 0.05)',
            'inner-glow': 'inset 0 0 60px rgba(0, 240, 255, 0.03)',
          },
          animation: {
            'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
            'spin-slow': 'spin 8s linear infinite',
            'float': 'float 6s ease-in-out infinite',
          },
          keyframes: {
            float: {
              '0%, 100%': { transform: 'translateY(0px)' },
              '50%': { transform: 'translateY(-10px)' },
            },
          },
        },
      },
    };
  </script>
  
  <!-- Polices Google Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
  
  <!-- Font Awesome 6 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
  
  <style>
    /* Styles personnalisés additionnels */
    body {
      background: linear-gradient(145deg, #0a0a0f 0%, #12121a 50%, #0d0d15 100%);
      min-height: 100vh;
      overflow-x: hidden;
    }
    
    .viewer-container {
      background: radial-gradient(circle at 50% 0%, rgba(0, 240, 255, 0.06) 0%, transparent 70%);
    }
    
    .product-image-container {
      background: linear-gradient(145deg, #16161f, #0d0d14);
      cursor: grab;
      position: relative;
      border: 1px solid rgba(255, 255, 255, 0.06);
    }
    
    .product-image-container:active {
      cursor: grabbing;
    }
    
    .product-image-container::before {
      content: '';
      position: absolute;
      inset: -1px;
      border-radius: inherit;
      padding: 1px;
      background: linear-gradient(135deg, rgba(0, 240, 255, 0.3), transparent 40%, transparent 60%, rgba(179, 71, 234, 0.3));
      mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
      mask-composite: exclude;
      pointer-events: none;
      z-index: 10;
    }
    
    .product-image-container img {
      pointer-events: none;
      user-select: none;
      -webkit-user-drag: none;
    }
    
    .btn-control {
      background: rgba(255, 255, 255, 0.03);
      border: 1px solid rgba(255, 255, 255, 0.08);
      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      position: relative;
      overflow: hidden;
    }
    
    .btn-control::before {
      content: '';
      position: absolute;
      inset: 0;
      background: radial-gradient(circle at center, rgba(0, 240, 255, 0.15), transparent 70%);
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    
    .btn-control:hover::before {
      opacity: 1;
    }
    
    .btn-control:hover {
      border-color: rgba(0, 240, 255, 0.4);
      transform: translateY(-2px);
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4), 0 0 20px rgba(0, 240, 255, 0.1);
    }
    
    .btn-control:active {
      transform: scale(0.95);
      transition: transform 0.1s ease;
    }
    
    .frame-badge {
      background: rgba(255, 255, 255, 0.03);
      border: 1px solid rgba(255, 255, 255, 0.08);
      backdrop-filter: blur(10px);
      letter-spacing: 0.05em;
    }
    
    .auto-rotate-btn {
      background: rgba(255, 255, 255, 0.03);
      border: 1px solid rgba(255, 255, 255, 0.08);
      transition: all 0.3s ease;
    }
    
    .auto-rotate-btn:hover {
      border-color: rgba(179, 71, 234, 0.5);
      box-shadow: 0 0 20px rgba(179, 71, 234, 0.1);
    }
    
    .auto-rotate-btn.active {
      background: rgba(179, 71, 234, 0.1);
      border-color: rgba(179, 71, 234, 0.6);
      box-shadow: 0 0 25px rgba(179, 71, 234, 0.2);
      color: #b347ea;
    }
    
    /* Animation de changement d'image */
    .fade-transition {
      transition: opacity 0.15s ease;
    }
    
    /* Scrollbar stylisée */
    ::-webkit-scrollbar {
      width: 6px;
    }
    ::-webkit-scrollbar-track {
      background: #0a0a0f;
    }
    ::-webkit-scrollbar-thumb {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 3px;
    }
    
    /* Effet de particules d'arrière-plan (optionnel) */
    .bg-particles {
      position: fixed;
      inset: 0;
      pointer-events: none;
      z-index: 0;
      opacity: 0.3;
      background-image: radial-gradient(circle at 20% 80%, rgba(0, 240, 255, 0.05) 0%, transparent 50%),
                        radial-gradient(circle at 80% 20%, rgba(179, 71, 234, 0.05) 0%, transparent 50%);
    }
  </style>
</head>
<body class="font-display text-white antialiased relative">

  <!-- Fond décoratif -->
  <div class="bg-particles"></div>

  <!-- Contenu principal -->
  <div class="relative z-10 min-h-screen flex flex-col">
    
    <!-- Navigation minimaliste -->
    <nav class="w-full px-6 py-5 flex items-center justify-between max-w-6xl mx-auto">
      <div class="flex items-center gap-3">
        <div class="w-10 h-10 rounded-full bg-gradient-to-br from-neon-cyan to-neon-purple flex items-center justify-center shadow-neon-cyan">
          <i class="fa-solid fa-cube text-white text-sm"></i>
        </div>
        <div>
          <h1 class="text-lg font-semibold tracking-tight leading-none">ThreeSixty<span class="text-neon-cyan">View</span></h1>
          <p class="text-xs text-gray-500 tracking-widest uppercase">Produit</p>
        </div>
      </div>
      <div class="hidden sm:flex items-center gap-4">
        <span class="text-xs text-gray-400 flex items-center gap-2">
          <span class="w-1.5 h-1.5 rounded-full bg-neon-cyan animate-pulse"></span>
          Rotation interactive
        </span>
      </div>
    </nav>

    <!-- Section principale avec la visionneuse -->
    <main class="flex-grow flex items-center justify-center px-4 py-8">
      <div class="w-full max-w-lg viewer-container">
        
        <!-- En-tête du produit -->
        <div class="text-center mb-8">
          <div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-glass border border-white/5 text-xs text-gray-400 mb-4 backdrop-blur-sm">
            <i class="fa-solid fa-rotate text-neon-cyan"></i>
            <span>Glissez pour pivoter</span>
          </div>
          <h2 class="text-3xl font-bold tracking-tight mb-2">Air Max Neon</h2>
          <p class="text-gray-400 text-sm">Édition limitée • Vue interactive 360°</p>
        </div>

        <!-- Visionneuse 360° -->
        <div class="relative mb-8">
          <!-- Conteneur de l'image avec effet de bordure -->
          <div class="product-image-container rounded-3xl overflow-hidden aspect-square shadow-2xl shadow-black/50" id="viewerWrapper">
            <img 
              id="productImage" 
              src="https://placehold.co/600x600/1a1a2e/00f0ff?text=Frame+01" 
              alt="Vue 360° du produit"
              class="w-full h-full object-contain fade-transition relative z-0"
            >
            
            <!-- Indicateur de progression sur le côté -->
            <div class="absolute right-4 top-1/2 -translate-y-1/2 z-20 flex flex-col items-center gap-1">
              <div class="w-0.5 h-32 bg-white/5 rounded-full relative overflow-hidden">
                <div class="absolute bottom-0 left-0 w-full bg-gradient-to-t from-neon-cyan to-neon-purple rounded-full transition-all duration-200" id="progressBar" style="height: 2.78%"></div>
              </div>
            </div>
            
            <!-- Badge du nombre de frames -->
            <div class="absolute bottom-4 left-4 z-20 frame-badge rounded-full px-3 py-1.5 text-xs font-mono text-gray-300 backdrop-blur-sm">
              <span id="currentFrame">01</span>/<span id="totalFrames">36</span>
            </div>
          </div>
        </div>

        <!-- Contrôles de rotation -->
        <div class="flex items-center justify-center gap-6 mb-6">
          <!-- Bouton gauche -->
          <button 
            id="rotateLeft" 
            class="btn-control w-14 h-14 rounded-full flex items-center justify-center group"
            title="Rotation précédente"
          >
            <i class="fa-solid fa-chevron-left text-gray-300 group-hover:text-neon-cyan transition-colors text-lg"></i>
          </button>
          
          <!-- Zone centrale avec info -->
          <div class="flex flex-col items-center gap-2 min-w-[100px]">
            <div class="flex items-center gap-2">
              <span class="w-2 h-2 rounded-full bg-neon-cyan shadow-neon-cyan"></span>
              <span class="text-xs text-gray-500 tracking-wider">FRAME</span>
              <span class="w-2 h-2 rounded-full bg-neon-purple shadow-neon-purple"></span>
            </div>
            <div class="font-mono text-2xl font-bold tabular-nums">
              <span id="frameNumber" class="text-neon-cyan">01</span>
            </div>
          </div>
          
          <!-- Bouton droit -->
          <button 
            id="rotateRight" 
            class="btn-control w-14 h-14 rounded-full flex items-center justify-center group"
            title="Rotation suivante"
          >
            <i class="fa-solid fa-chevron-right text-gray-300 group-hover:text-neon-cyan transition-colors text-lg"></i>
          </button>
        </div>

        <!-- Contrôles supplémentaires -->
        <div class="flex items-center justify-center gap-4 flex-wrap">
          <!-- Bouton auto-rotation -->
          <button 
            id="autoRotateToggle" 
            class="auto-rotate-btn px-5 py-2.5 rounded-full text-sm font-medium flex items-center gap-2 transition-all"
          >
            <i class="fa-solid fa-play text-xs"></i>
            <span>Auto-rotation</span>
          </button>
        </div>

        <!-- Indice tactile -->
        <div class="text-center mt-6">
          <p class="text-xs text-gray-600 flex items-center justify-center gap-1.5">
            <i class="fa-regular fa-hand-pointer"></i>
            Cliquez-glissez sur l'image
          </p>
        </div>
      </div>
    </main>

    <!-- Pied de page minimal -->
    <footer class="py-6 text-center">
      <p class="text-xs text-gray-700">
        © 2026 ThreeSixtyView <span class="mx-2">•</span> 
        <span class="text-gray-600">Expérience immersive</span>
      </p>
    </footer>
  </div>

  <!-- Script 360° -->
  <script>
    (function() {
      // ============ CONFIGURATION ============
      const TOTAL_FRAMES = 36;
      const IMAGE_PATH_PREFIX = 'https://placehold.co/600x600/1a1a2e/00f0ff?text=Frame+';
      const AUTO_ROTATE_INTERVAL = 80; // ms
      const DRAG_SENSITIVITY = 15; // pixels par frame
      
      // ============ ÉTAT ============
      let currentFrame = 1;
      let isDragging = false;
      let dragStartX = 0;
      let dragStartFrame = 1;
      let autoRotateTimer = null;
      let isAutoRotating = false;
      
      // ============ ÉLÉMENTS DOM ============
      const imgEl = document.getElementById('productImage');
      const wrapper = document.getElementById('viewerWrapper');
      const currentFrameEl = document.getElementById('currentFrame');
      const frameNumberEl = document.getElementById('frameNumber');
      const totalFramesEl = document.getElementById('totalFrames');
      const progressBar = document.getElementById('progressBar');
      const rotateLeftBtn = document.getElementById('rotateLeft');
      const rotateRightBtn = document.getElementById('rotateRight');
      const autoRotateBtn = document.getElementById('autoRotateToggle');
      
      // Initialisation des totaux
      totalFramesEl.textContent = String(TOTAL_FRAMES).padStart(2, '0');
      
      // ============ FONCTIONS ============
      function getImageUrl(frame) {
        const pad = String(frame).padStart(2, '0');
        return `${IMAGE_PATH_PREFIX}${pad}`;
      }
      
      function updateUI(frame) {
        // Normalisation
        let f = frame;
        if (f < 1) f = TOTAL_FRAMES;
        if (f > TOTAL_FRAMES) f = 1;
        currentFrame = f;
        
        // Mise à jour de l'image avec transition
        imgEl.style.opacity = '0.6';
        imgEl.src = getImageUrl(currentFrame);
        
        imgEl.onload = () => {
          imgEl.style.opacity = '1';
        };
        
        setTimeout(() => {
          imgEl.style.opacity = '1';
        }, 50);
        
        // Mise à jour des compteurs
        const paddedFrame = String(currentFrame).padStart(2, '0');
        currentFrameEl.textContent = paddedFrame;
        frameNumberEl.textContent = paddedFrame;
        
        // Mise à jour de la barre de progression
        const progressPercent = ((currentFrame - 1) / (TOTAL_FRAMES - 1)) * 100;
        progressBar.style.height = `${progressPercent}%`;
      }
      
      function rotateLeft() {
        updateUI(currentFrame - 1);
      }
      
      function rotateRight() {
        updateUI(currentFrame + 1);
      }
      
      // ============ AUTO-ROTATION ============
      function startAutoRotate() {
        if (autoRotateTimer) return;
        isAutoRotating = true;
        autoRotateBtn.classList.add('active');
        autoRotateBtn.querySelector('i').className = 'fa-solid fa-pause text-xs';
        autoRotateBtn.querySelector('span').textContent = 'Pause';
        
        autoRotateTimer = setInterval(() => {
          rotateRight();
        }, AUTO_ROTATE_INTERVAL);
      }
      
      function stopAutoRotate() {
        if (autoRotateTimer) {
          clearInterval(autoRotateTimer);
          autoRotateTimer = null;
        }
        isAutoRotating = false;
        autoRotateBtn.classList.remove('active');
        autoRotateBtn.querySelector('i').className = 'fa-solid fa-play text-xs';
        autoRotateBtn.querySelector('span').textContent = 'Auto-rotation';
      }
      
      function toggleAutoRotate() {
        isAutoRotating ? stopAutoRotate() : startAutoRotate();
      }
      
      // ============ GESTION DU DRAG ============
      function getEventX(e) {
        return e.touches ? e.touches[0].clientX : e.clientX;
      }
      
      function onDragStart(e) {
        if (e.target.closest('button')) return;
        e.preventDefault();
        
        isDragging = true;
        dragStartX = getEventX(e);
        dragStartFrame = currentFrame;
        
        if (isAutoRotating) stopAutoRotate();
        
        wrapper.style.cursor = 'grabbing';
      }
      
      function onDragMove(e) {
        if (!isDragging) return;
        e.preventDefault();
        
        const currentX = getEventX(e);
        const deltaX = currentX - dragStartX;
        const frameShift = Math.round(deltaX / DRAG_SENSITIVITY);
        
        let newFrame = dragStartFrame + frameShift;
        
        // Normalisation avec modulo
        newFrame = ((newFrame - 1) % TOTAL_FRAMES + TOTAL_FRAMES) % TOTAL_FRAMES + 1;
        
        if (newFrame !== currentFrame) {
          updateUI(newFrame);
        }
      }
      
      function onDragEnd(e) {
        if (!isDragging) return;
        isDragging = false;
        wrapper.style.cursor = 'grab';
        dragStartX = 0;
        dragStartFrame = currentFrame;
      }
      
      // ============ ÉVÉNEMENTS ============
      wrapper.addEventListener('mousedown', onDragStart);
      window.addEventListener('mousemove', onDragMove);
      window.addEventListener('mouseup', onDragEnd);
      window.addEventListener('mouseleave', onDragEnd);
      
      wrapper.addEventListener('touchstart', onDragStart, { passive: false });
      window.addEventListener('touchmove', onDragMove, { passive: false });
      window.addEventListener('touchend', onDragEnd);
      window.addEventListener('touchcancel', onDragEnd);
      
      // Empêcher le scroll pendant le drag sur mobile
      document.addEventListener('touchmove', function(e) {
        if (isDragging) e.preventDefault();
      }, { passive: false });
      
      // Boutons
      rotateLeftBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        if (isAutoRotating) stopAutoRotate();
        rotateLeft();
      });
      
      rotateRightBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        if (isAutoRotating) stopAutoRotate();
        rotateRight();
      });
      
      autoRotateBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        toggleAutoRotate();
      });
      
      // Raccourcis clavier
      document.addEventListener('keydown', (e) => {
        if (e.key === 'ArrowLeft') {
          e.preventDefault();
          if (isAutoRotating) stopAutoRotate();
          rotateLeft();
        } else if (e.key === 'ArrowRight') {
          e.preventDefault();
          if (isAutoRotating) stopAutoRotate();
          rotateRight();
        } else if (e.key === ' ') {
          e.preventDefault();
          toggleAutoRotate();
        }
      });
      
      // Curseur par défaut
      wrapper.style.cursor = 'grab';
      
      // Initialisation
      updateUI(1);
      
      // Nettoyage
      window.addEventListener('beforeunload', () => {
        stopAutoRotate();
      });
      
      console.log('🚀 ThreeSixtyView • Thème sombre activé •', TOTAL_FRAMES, 'frames');
    })();
  </script>
</body>
</html>

Télécharger le fichier source

Partager