| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
document.querySelectorAll('.bkbg-p360-app').forEach(function (app) { |
| 5 |
var o; |
| 6 |
try { o = JSON.parse(app.dataset.opts || '{}'); } catch (e) { return; } |
| 7 |
|
| 8 |
var frames = Array.isArray(o.frames) ? o.frames : []; |
| 9 |
var viewerWidth = o.viewerWidth || 600; |
| 10 |
var viewerHeight = o.viewerHeight || 400; |
| 11 |
var viewerRadius = o.viewerRadius || 16; |
| 12 |
var autoSpin = !!o.autoSpin; |
| 13 |
var autoSpeed = o.autoSpeed || 60; |
| 14 |
var dragSensitivity = o.dragSensitivity || 4; |
| 15 |
var showProgress = !!o.showProgress; |
| 16 |
var showHint = !!o.showHint; |
| 17 |
var hintText = o.hintText || 'Drag to rotate'; |
| 18 |
var showControls = !!o.showControls; |
| 19 |
var bgColor = o.bgColor || '#f1f5f9'; |
| 20 |
var progressColor = o.progressColor || '#6366f1'; |
| 21 |
var hintColor = o.hintColor || '#64748b'; |
| 22 |
var controlsBg = o.controlsBg || '#ffffff'; |
| 23 |
var controlsColor = o.controlsColor || '#0f172a'; |
| 24 |
|
| 25 |
var _typoKeys = { hintTypo: '--bkbg-p360-hn-' }; |
| 26 |
function typoCssVarsForEl(el) { |
| 27 |
Object.keys(_typoKeys).forEach(function (attr) { |
| 28 |
var t = o[attr]; if (!t || typeof t !== 'object') return; |
| 29 |
var p = _typoKeys[attr]; |
| 30 |
if (t.family) el.style.setProperty(p + 'font-family', t.family); |
| 31 |
if (t.weight) el.style.setProperty(p + 'font-weight', t.weight); |
| 32 |
if (t.transform) el.style.setProperty(p + 'text-transform', t.transform); |
| 33 |
if (t.style) el.style.setProperty(p + 'font-style', t.style); |
| 34 |
if (t.decoration) el.style.setProperty(p + 'text-decoration', t.decoration); |
| 35 |
if (t.sizeDesktop) el.style.setProperty(p + 'font-size-d', t.sizeDesktop + (t.sizeUnit || 'px')); |
| 36 |
if (t.sizeTablet) el.style.setProperty(p + 'font-size-t', t.sizeTablet + (t.sizeUnit || 'px')); |
| 37 |
if (t.sizeMobile) el.style.setProperty(p + 'font-size-m', t.sizeMobile + (t.sizeUnit || 'px')); |
| 38 |
if (t.lineHeightDesktop) el.style.setProperty(p + 'line-height-d', t.lineHeightDesktop + (t.lineHeightUnit || '')); |
| 39 |
if (t.lineHeightTablet) el.style.setProperty(p + 'line-height-t', t.lineHeightTablet + (t.lineHeightUnit || '')); |
| 40 |
if (t.lineHeightMobile) el.style.setProperty(p + 'line-height-m', t.lineHeightMobile + (t.lineHeightUnit || '')); |
| 41 |
if (t.letterSpacingDesktop) el.style.setProperty(p + 'letter-spacing-d', t.letterSpacingDesktop + (t.letterSpacingUnit || 'px')); |
| 42 |
if (t.letterSpacingTablet) el.style.setProperty(p + 'letter-spacing-t', t.letterSpacingTablet + (t.letterSpacingUnit || 'px')); |
| 43 |
if (t.letterSpacingMobile) el.style.setProperty(p + 'letter-spacing-m', t.letterSpacingMobile + (t.letterSpacingUnit || 'px')); |
| 44 |
if (t.wordSpacingDesktop) el.style.setProperty(p + 'word-spacing-d', t.wordSpacingDesktop + (t.wordSpacingUnit || 'px')); |
| 45 |
if (t.wordSpacingTablet) el.style.setProperty(p + 'word-spacing-t', t.wordSpacingTablet + (t.wordSpacingUnit || 'px')); |
| 46 |
if (t.wordSpacingMobile) el.style.setProperty(p + 'word-spacing-m', t.wordSpacingMobile + (t.wordSpacingUnit || 'px')); |
| 47 |
}); |
| 48 |
} |
| 49 |
|
| 50 |
if (frames.length === 0) return; |
| 51 |
|
| 52 |
typoCssVarsForEl(app); |
| 53 |
|
| 54 |
var total = frames.length; |
| 55 |
var current = 0; |
| 56 |
|
| 57 |
/* ── Build DOM ── */ |
| 58 |
var viewer = document.createElement('div'); |
| 59 |
viewer.className = 'bkbg-p360-viewer'; |
| 60 |
viewer.style.width = '100%'; |
| 61 |
viewer.style.maxWidth = viewerWidth + 'px'; |
| 62 |
viewer.style.height = viewerHeight + 'px'; |
| 63 |
viewer.style.borderRadius = viewerRadius + 'px'; |
| 64 |
viewer.style.background = bgColor; |
| 65 |
viewer.style.setProperty('--p360-progress', progressColor); |
| 66 |
|
| 67 |
var img = document.createElement('img'); |
| 68 |
img.className = 'bkbg-p360-img'; |
| 69 |
img.alt = frames[0].alt || ''; |
| 70 |
img.src = frames[0].url; |
| 71 |
viewer.appendChild(img); |
| 72 |
|
| 73 |
/* Progress */ |
| 74 |
var progressEl = null; |
| 75 |
if (showProgress) { |
| 76 |
progressEl = document.createElement('div'); |
| 77 |
progressEl.className = 'bkbg-p360-progress'; |
| 78 |
progressEl.style.width = '0%'; |
| 79 |
viewer.appendChild(progressEl); |
| 80 |
} |
| 81 |
|
| 82 |
/* Hint */ |
| 83 |
var hintEl = null; |
| 84 |
if (showHint) { |
| 85 |
hintEl = document.createElement('div'); |
| 86 |
hintEl.className = 'bkbg-p360-hint'; |
| 87 |
hintEl.textContent = hintText; |
| 88 |
hintEl.style.background = 'rgba(0,0,0,0.45)'; |
| 89 |
hintEl.style.color = '#ffffff'; |
| 90 |
viewer.appendChild(hintEl); |
| 91 |
} |
| 92 |
|
| 93 |
app.appendChild(viewer); |
| 94 |
|
| 95 |
/* Controls */ |
| 96 |
var counterEl = null; |
| 97 |
if (showControls) { |
| 98 |
var controls = document.createElement('div'); |
| 99 |
controls.className = 'bkbg-p360-controls'; |
| 100 |
|
| 101 |
var btnPrev = document.createElement('button'); |
| 102 |
btnPrev.className = 'bkbg-p360-btn'; |
| 103 |
btnPrev.textContent = '←'; |
| 104 |
btnPrev.style.background = controlsBg; |
| 105 |
btnPrev.style.color = controlsColor; |
| 106 |
|
| 107 |
counterEl = document.createElement('div'); |
| 108 |
counterEl.className = 'bkbg-p360-counter'; |
| 109 |
counterEl.style.color = controlsColor; |
| 110 |
|
| 111 |
var btnNext = document.createElement('button'); |
| 112 |
btnNext.className = 'bkbg-p360-btn'; |
| 113 |
btnNext.textContent = '→'; |
| 114 |
btnNext.style.background = controlsBg; |
| 115 |
btnNext.style.color = controlsColor; |
| 116 |
|
| 117 |
controls.appendChild(btnPrev); |
| 118 |
controls.appendChild(counterEl); |
| 119 |
controls.appendChild(btnNext); |
| 120 |
app.appendChild(controls); |
| 121 |
|
| 122 |
btnPrev.addEventListener('click', function () { goTo(current - 1); stopHint(); }); |
| 123 |
btnNext.addEventListener('click', function () { goTo(current + 1); stopHint(); }); |
| 124 |
} |
| 125 |
|
| 126 |
function goTo(idx) { |
| 127 |
current = ((idx % total) + total) % total; |
| 128 |
img.src = frames[current].url; |
| 129 |
img.alt = frames[current].alt || ''; |
| 130 |
if (progressEl) { |
| 131 |
progressEl.style.width = ((current / (total - 1)) * 100) + '%'; |
| 132 |
} |
| 133 |
if (counterEl) { |
| 134 |
counterEl.textContent = (current + 1) + ' / ' + total; |
| 135 |
} |
| 136 |
} |
| 137 |
|
| 138 |
goTo(0); |
| 139 |
|
| 140 |
/* ── Drag interaction ── */ |
| 141 |
var dragging = false; |
| 142 |
var startX = 0; |
| 143 |
var accumulated = 0; |
| 144 |
|
| 145 |
function stopHint() { |
| 146 |
if (hintEl && !hintEl.classList.contains('bkbg-p360-fade')) { |
| 147 |
hintEl.classList.add('bkbg-p360-fade'); |
| 148 |
setTimeout(function () { if (hintEl && hintEl.parentNode) hintEl.parentNode.removeChild(hintEl); hintEl = null; }, 400); |
| 149 |
} |
| 150 |
} |
| 151 |
|
| 152 |
function getClientX(e) { |
| 153 |
return e.touches ? e.touches[0].clientX : e.clientX; |
| 154 |
} |
| 155 |
|
| 156 |
viewer.addEventListener('mousedown', function (e) { |
| 157 |
dragging = true; |
| 158 |
startX = getClientX(e); |
| 159 |
accumulated = 0; |
| 160 |
viewer.classList.add('bkbg-p360-dragging'); |
| 161 |
stopHint(); |
| 162 |
}); |
| 163 |
|
| 164 |
viewer.addEventListener('touchstart', function (e) { |
| 165 |
dragging = true; |
| 166 |
startX = getClientX(e); |
| 167 |
accumulated = 0; |
| 168 |
stopHint(); |
| 169 |
}, { passive: true }); |
| 170 |
|
| 171 |
document.addEventListener('mousemove', function (e) { |
| 172 |
if (!dragging) return; |
| 173 |
var dx = getClientX(e) - startX; |
| 174 |
accumulated += dx; |
| 175 |
startX = getClientX(e); |
| 176 |
var step = Math.round(accumulated / dragSensitivity); |
| 177 |
if (step !== 0) { |
| 178 |
goTo(current + step); |
| 179 |
accumulated = 0; |
| 180 |
} |
| 181 |
}); |
| 182 |
|
| 183 |
document.addEventListener('touchmove', function (e) { |
| 184 |
if (!dragging) return; |
| 185 |
var dx = getClientX(e) - startX; |
| 186 |
accumulated += dx; |
| 187 |
startX = getClientX(e); |
| 188 |
var step = Math.round(accumulated / dragSensitivity); |
| 189 |
if (step !== 0) { |
| 190 |
goTo(current - step); |
| 191 |
accumulated = 0; |
| 192 |
} |
| 193 |
}, { passive: true }); |
| 194 |
|
| 195 |
function endDrag() { |
| 196 |
dragging = false; |
| 197 |
viewer.classList.remove('bkbg-p360-dragging'); |
| 198 |
} |
| 199 |
document.addEventListener('mouseup', endDrag); |
| 200 |
document.addEventListener('touchend', endDrag); |
| 201 |
|
| 202 |
/* ── Auto spin ── */ |
| 203 |
if (autoSpin && total > 1) { |
| 204 |
var interval = 1000 / autoSpeed; |
| 205 |
var spinAcc = 0; |
| 206 |
var lastTime = null; |
| 207 |
var rafId = null; |
| 208 |
|
| 209 |
function spin(ts) { |
| 210 |
if (!lastTime) lastTime = ts; |
| 211 |
var dt = ts - lastTime; |
| 212 |
lastTime = ts; |
| 213 |
|
| 214 |
if (!dragging) { |
| 215 |
spinAcc += dt; |
| 216 |
while (spinAcc >= interval) { |
| 217 |
goTo(current + 1); |
| 218 |
spinAcc -= interval; |
| 219 |
} |
| 220 |
} |
| 221 |
rafId = requestAnimationFrame(spin); |
| 222 |
} |
| 223 |
|
| 224 |
var observer = new IntersectionObserver(function (entries) { |
| 225 |
entries.forEach(function (entry) { |
| 226 |
if (entry.isIntersecting) { |
| 227 |
lastTime = null; |
| 228 |
rafId = requestAnimationFrame(spin); |
| 229 |
} else { |
| 230 |
if (rafId) { cancelAnimationFrame(rafId); rafId = null; } |
| 231 |
} |
| 232 |
}); |
| 233 |
}, { threshold: 0.1 }); |
| 234 |
observer.observe(app); |
| 235 |
} |
| 236 |
}); |
| 237 |
}()); |
| 238 |
|