User:Monster Domosed/common.js: Difference between revisionsGive feedback
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
( function () { | /** | ||
* Rotatable Model Viewer — with 2D / 3D toggle | |||
*/ | |||
(function () { | |||
'use strict'; | 'use strict'; | ||
function boot(el) { | |||
function boot( el ) { | if (el.dataset.rmReady) return; | ||
if ( el.dataset.rmReady ) return; | |||
el.dataset.rmReady = '1'; | el.dataset.rmReady = '1'; | ||
/* --- config --- */ | /* ---- config from data-attributes ---- */ | ||
var imageUrl = el.dataset.image, | var imageUrl = el.dataset.image || '', | ||
image2dUrl = el.dataset.image2d || '', | |||
fw = +el.dataset.frameWidth || 300, | fw = +el.dataset.frameWidth || 300, | ||
fh = +el.dataset.frameHeight || 400, | fh = +el.dataset.frameHeight || 400, | ||
| Line 16: | Line 18: | ||
sensitivity = +el.dataset.sensitivity || 5; | sensitivity = +el.dataset.sensitivity || 5; | ||
var hint = el.querySelector('.rotatable-model-hint'), | |||
load = el.querySelector('.rotatable-model-loading'), | |||
wrap = el.closest('.rotatable-model-wrapper'); | |||
if (!imageUrl) { | |||
if (load) load.textContent = 'Error: no image URL'; | |||
return; | |||
} | |||
/* | /* ============================================================ | ||
var img | STATE | ||
============================================================ */ | |||
var cur = startFrame, | |||
is2D = false, // current mode | |||
dragOK = true; // allow drag interaction | |||
/* ============================================================ | |||
3D SPRITE STRIP | |||
============================================================ */ | |||
var img = new Image(); | |||
img.className = 'rm-strip'; | img.className = 'rm-strip'; | ||
img.alt = ''; | img.alt = ''; | ||
img.draggable = false; | img.draggable = false; | ||
img.style.height = fh + 'px'; | img.style.height = fh + 'px'; | ||
img.style.width = ( fw * count ) + 'px'; | img.style.width = (fw * count) + 'px'; | ||
img. | img.addEventListener('load', function () { | ||
if ( load ) load.style.display = 'none'; | var detected = Math.round(img.naturalWidth / fw); | ||
}; | if (detected > 0 && detected !== count) count = detected; | ||
img. | if (load) load.style.display = 'none'; | ||
if ( load ) load.textContent = ' | show(startFrame); | ||
}; | }); | ||
img.addEventListener('error', function () { | |||
if (load) { load.textContent = 'Failed to load image'; load.style.color = '#d33'; } | |||
}); | |||
img.src = imageUrl; | img.src = imageUrl; | ||
el.insertBefore( img, el.firstChild ); | el.insertBefore(img, el.firstChild); | ||
if (img.complete && img.naturalWidth > 0) { | |||
if (load) load.style.display = 'none'; | |||
} | |||
/* ============================================================ | |||
2D STATIC IMAGE (created only if URL provided) | |||
============================================================ */ | |||
var staticImg = null; | |||
if (image2dUrl) { | |||
staticImg = new Image(); | |||
staticImg.className = 'rm-static'; | |||
staticImg.alt = ''; | |||
staticImg.draggable = false; | |||
staticImg.src = image2dUrl; | |||
el.insertBefore(staticImg, el.firstChild); | |||
} | |||
/* | /* ============================================================ | ||
function show( idx ) { | FRAME DISPLAY | ||
cur = ( ( idx % count ) + count ) % count; | ============================================================ */ | ||
img.style.left = -( cur * fw ) + 'px'; | function show(idx) { | ||
cur = ((idx % count) + count) % count; | |||
img.style.left = -(cur * fw) + 'px'; | |||
} | } | ||
function nudge(d) { if (!is2D) { show(cur - d); hideHint(); } } | |||
function reset() { show(startFrame); } | |||
function hideHint() { if (hint) hint.classList.add('rm-hidden'); } | |||
/* ============================================================ | |||
2D ↔ 3D MODE SWITCHING | |||
============================================================ */ | |||
var btn2d = el.querySelector('.rm-mode-2d'), | |||
btn3d = el.querySelector('.rm-mode-3d'); | |||
function | function setMode(mode) { | ||
is2D = (mode === '2d'); | |||
if (is2D) { | |||
el.classList.add('rm-is-2d'); | |||
if (wrap) wrap.classList.add('rm-is-2d'); | |||
dragOK = false; | |||
} else { | |||
el.classList.remove('rm-is-2d'); | |||
if (wrap) wrap.classList.remove('rm-is-2d'); | |||
dragOK = true; | |||
} | |||
// Toggle active button | |||
if (btn2d && btn3d) { | |||
btn2d.classList.toggle('rm-active', is2D); | |||
btn3d.classList.toggle('rm-active', !is2D); | |||
} | |||
} | } | ||
/* | if (btn2d) { | ||
function startDrag( x0 ) { | btn2d.addEventListener('click', function () { setMode('2d'); }); | ||
} | |||
if (btn3d) { | |||
btn3d.addEventListener('click', function () { setMode('3d'); }); | |||
} | |||
/* ============================================================ | |||
DRAG (mouse + touch) | |||
============================================================ */ | |||
function startDrag(x0) { | |||
if (!dragOK) return; | |||
var acc = 0; | var acc = 0; | ||
hideHint(); | hideHint(); | ||
function move( cx ) { | function move(cx) { | ||
acc += cx - x0; | acc += cx - x0; | ||
x0 | x0 = cx; | ||
var fd = ( acc / sensitivity ) | 0; | var fd = (acc / sensitivity) | 0; | ||
if ( fd ) { | if (fd) { acc -= fd * sensitivity; show(cur + fd); } | ||
} | } | ||
function onMM( e ) { move( e.clientX ); e.preventDefault(); } | function onMM(e) { move(e.clientX); e.preventDefault(); } | ||
function onTM( e ) { | function onTM(e) { | ||
if ( e.touches.length === 1 ) { | if (e.touches.length === 1) { move(e.touches[0].clientX); e.preventDefault(); } | ||
} | } | ||
function stop() { | function stop() { | ||
el.classList.remove( 'rm-dragging' ); | el.classList.remove('rm-dragging'); | ||
document.removeEventListener( 'mousemove', onMM ); | document.removeEventListener('mousemove', onMM); | ||
document.removeEventListener( 'mouseup', | document.removeEventListener('mouseup', stop); | ||
document.removeEventListener( 'touchmove', onTM ); | document.removeEventListener('touchmove', onTM); | ||
document.removeEventListener( 'touchend', | document.removeEventListener('touchend', stop); | ||
document.removeEventListener( 'touchcancel', stop ); | document.removeEventListener('touchcancel', stop); | ||
} | } | ||
el.classList.add( 'rm-dragging' ); | el.classList.add('rm-dragging'); | ||
document.addEventListener( 'mousemove', onMM ); | document.addEventListener('mousemove', onMM); | ||
document.addEventListener( 'mouseup', | document.addEventListener('mouseup', stop); | ||
document.addEventListener( 'touchmove', onTM, { passive: false } ); | document.addEventListener('touchmove', onTM, { passive: false }); | ||
document.addEventListener( 'touchend', | document.addEventListener('touchend', stop); | ||
document.addEventListener( 'touchcancel', stop ); | document.addEventListener('touchcancel', stop); | ||
} | } | ||
el.addEventListener( 'mousedown', function ( e ) { | el.addEventListener('mousedown', function (e) { | ||
if ( e.button === 0 ) { startDrag( e.clientX ); e.preventDefault(); } | // Don't drag if clicking a mode button | ||
if (e.target.closest('.rm-mode-toggle')) return; | |||
if (e.button === 0 && dragOK) { startDrag(e.clientX); e.preventDefault(); } | |||
}); | }); | ||
el.addEventListener( 'touchstart', function ( e ) { | el.addEventListener('touchstart', function (e) { | ||
if ( e.touches.length === 1 ) { startDrag( e.touches[0].clientX ); e.preventDefault(); } | if (e.target.closest('.rm-mode-toggle')) return; | ||
}, { passive: false } ); | if (e.touches.length === 1 && dragOK) { | ||
startDrag(e.touches[0].clientX); e.preventDefault(); | |||
} | |||
}, { passive: false }); | |||
/* --- | /* ---- wheel ---- */ | ||
el.addEventListener( 'wheel', function ( e ) { | el.addEventListener('wheel', function (e) { | ||
if (!dragOK) return; | |||
var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0; | var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0; | ||
if ( d ) { nudge( d ); e.preventDefault(); } | if (d) { nudge(d); e.preventDefault(); } | ||
}, { passive: false } ); | }, { passive: false }); | ||
/* --- keyboard | /* ---- keyboard ---- */ | ||
el.setAttribute( 'tabindex', '0' ); | el.setAttribute('tabindex', '0'); | ||
el.addEventListener( 'keydown', function ( e ) { | el.addEventListener('keydown', function (e) { | ||
if (is2D) return; | |||
if (e.key === 'ArrowLeft') { nudge(1); e.preventDefault(); } | |||
if (e.key === 'ArrowRight') { nudge(1); e.preventDefault(); } | |||
if (e.key === 'Home') { reset(); e.preventDefault(); } | |||
}); | }); | ||
/* --- nav buttons | /* ---- nav buttons ---- */ | ||
if (wrap) { | |||
if ( wrap ) { | var bL = wrap.querySelector('.rm-btn-left'), | ||
var bL = wrap.querySelector( '.rm-btn-left' ), | bR = wrap.querySelector('.rm-btn-right'), | ||
bR = wrap.querySelector( '.rm-btn-right' ), | bO = wrap.querySelector('.rm-btn-reset'); | ||
bO = wrap.querySelector( '.rm-btn-reset' ); | if (bL) bL.addEventListener('click', function () { nudge(-1); }); | ||
if (bR) bR.addEventListener('click', function () { nudge( 1); }); | |||
if ( bL ) bL.addEventListener( 'click', function () { nudge( 1 ); } ); | if (bO) bO.addEventListener('click', reset); | ||
if ( bR ) bR.addEventListener( 'click', function () { nudge( | |||
if ( bO ) bO.addEventListener( 'click', reset ); | |||
} | } | ||
/* --- initial frame --- */ | /* ---- initial frame ---- */ | ||
show( startFrame ); | show(startFrame); | ||
} | } | ||
/* ---- scan & initialise ---- */ | /* ---- scan & initialise ---- */ | ||
function initAll( root ) { | function initAll(root) { | ||
var els = ( root || document ).querySelectorAll( | var els = (root || document).querySelectorAll('.rotatable-model'); | ||
for (var i = 0; i < els.length; i++) boot(els[i]); | |||
for ( var i = 0; i < els.length; i++ ) boot( els[i] ); | |||
} | } | ||
if ( document.readyState === 'loading' ) { | if (document.readyState === 'loading') { | ||
document.addEventListener( 'DOMContentLoaded', function () { initAll(); } ); | document.addEventListener('DOMContentLoaded', function () { initAll(); }); | ||
} else { | } else { | ||
initAll(); | initAll(); | ||
} | } | ||
if (typeof mw !== 'undefined' && mw.hook) { | |||
if ( typeof mw !== 'undefined' && mw.hook ) { | mw.hook('wikipage.content').add(function ($c) { initAll($c[0]); }); | ||
mw.hook( 'wikipage.content' ).add( function ( $c ) { initAll( $c[0] ); } ); | |||
} | } | ||
}() ); | }()); | ||