User:Monster Domosed/common.js: Difference between revisionsGive feedback
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
( function () { | |||
'use strict'; | |||
/* ---- bootstrap one viewer element ---- */ | |||
function boot( el ) { | |||
if ( el.dataset.rmReady ) return; // already initialised | |||
el.dataset.rmReady = '1'; | |||
/* --- config --- */ | |||
var imageUrl = el.dataset.image, | |||
fw = +el.dataset.frameWidth || 300, | |||
fh = +el.dataset.frameHeight || 400, | |||
count = +el.dataset.frameCount || 24, | |||
startFrame = +el.dataset.startFrame || 0, | |||
sensitivity = +el.dataset.sensitivity || 5; | |||
if ( !imageUrl ) return; | |||
var cur = startFrame, | |||
hint = el.querySelector( '.rotatable-model-hint' ), | |||
load = el.querySelector( '.rotatable-model-loading' ); | |||
/* --- build the <img> strip --- */ | |||
} | var img = document.createElement( 'img' ); | ||
img.className = 'rm-strip'; | |||
img.alt = ''; | |||
img.draggable = false; | |||
img.style.height = fh + 'px'; | |||
img.style.width = ( fw * count ) + 'px'; | |||
img.onload = function () { | |||
if ( load ) load.style.display = 'none'; | |||
}; | |||
img.onerror = function () { | |||
if ( load ) load.textContent = 'Image failed to load'; | |||
}; | |||
img.src = imageUrl; | |||
el.insertBefore( img, el.firstChild ); | |||
/* --- helpers --- */ | |||
function show( idx ) { | |||
cur = ( ( idx % count ) + count ) % count; | |||
img.style.left = -( cur * fw ) + 'px'; | |||
} | |||
function nudge( d ) { show( cur + d ); hideHint(); } | |||
function reset() { show( startFrame ); } | |||
function hideHint() { | |||
if ( hint ) hint.classList.add( 'rm-hidden' ); | |||
} | |||
/* --- drag (mouse + touch) --- */ | |||
function startDrag( x0 ) { | |||
var acc = 0; | |||
hideHint(); | |||
function move( cx ) { | |||
acc += cx - x0; | |||
x0 = cx; | |||
var fd = ( acc / sensitivity ) | 0; // truncate → 0 | |||
if ( fd ) { | |||
acc -= fd * sensitivity; | |||
show( cur + fd ); | |||
} | |||
} | |||
function onMM( e ) { move( e.clientX ); e.preventDefault(); } | |||
function onTM( e ) { | |||
if ( e.touches.length === 1 ) { | |||
move( e.touches[0].clientX ); | |||
e.preventDefault(); | |||
} | |||
} | |||
function stop() { | |||
el.classList.remove( 'rm-dragging' ); | |||
document.removeEventListener( 'mousemove', onMM ); | |||
document.removeEventListener( 'mouseup', stop ); | |||
document.removeEventListener( 'touchmove', onTM ); | |||
document.removeEventListener( 'touchend', stop ); | |||
document.removeEventListener( 'touchcancel', stop ); | |||
} | |||
el.classList.add( 'rm-dragging' ); | |||
document.addEventListener( 'mousemove', onMM ); | |||
document.addEventListener( 'mouseup', stop ); | |||
document.addEventListener( 'touchmove', onTM, { passive: false } ); | |||
document.addEventListener( 'touchend', stop ); | |||
document.addEventListener( 'touchcancel', stop ); | |||
} | |||
el.addEventListener( 'mousedown', function ( e ) { | |||
if ( e.button === 0 ) { startDrag( e.clientX ); e.preventDefault(); } | |||
}); | |||
el.addEventListener( 'touchstart', function ( e ) { | |||
if ( e.touches.length === 1 ) { startDrag( e.touches[0].clientX ); e.preventDefault(); } | |||
}, { passive: false } ); | |||
/* --- mouse-wheel --- */ | |||
el.addEventListener( 'wheel', function ( e ) { | |||
var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0; | |||
if ( d ) { nudge( d ); e.preventDefault(); } | |||
}, { passive: false } ); | |||
/* --- keyboard (element must be focusable) --- */ | |||
el.setAttribute( 'tabindex', '0' ); | |||
el.addEventListener( 'keydown', function ( e ) { | |||
switch ( e.key ) { | |||
case 'ArrowLeft': nudge( -1 ); e.preventDefault(); break; | |||
case 'ArrowRight': nudge( 1 ); e.preventDefault(); break; | |||
case 'Home': reset(); e.preventDefault(); break; | |||
} | |||
}); | |||
/* --- nav buttons (outside .rotatable-model, inside wrapper) --- */ | |||
var wrap = el.closest( '.rotatable-model-wrapper' ); | |||
if ( wrap ) { | |||
var bL = wrap.querySelector( '.rm-btn-left' ), | |||
bR = wrap.querySelector( '.rm-btn-right' ), | |||
bO = wrap.querySelector( '.rm-btn-reset' ); | |||
if ( bL ) bL.addEventListener( 'click', function () { nudge( -1 ); } ); | |||
if ( bR ) bR.addEventListener( 'click', function () { nudge( 1 ); } ); | |||
if ( bO ) bO.addEventListener( 'click', reset ); | |||
} | |||
/* --- initial frame --- */ | |||
show( startFrame ); | |||
} | } | ||
/* ---- scan & initialise ---- */ | |||
function initAll( root ) { | |||
var els = ( root || document ).querySelectorAll( | |||
'.rotatable-model:not([data-rm-ready])' ); | |||
for ( var i = 0; i < els.length; i++ ) boot( els[i] ); | |||
} | |||
if ( document.readyState === 'loading' ) { | |||
document.addEventListener( 'DOMContentLoaded', function () { initAll(); } ); | |||
} else { | |||
initAll(); | |||
} | |||
// re-scan when MediaWiki injects content (VE save, live-preview, etc.) | |||
if ( typeof mw !== 'undefined' && mw.hook ) { | |||
mw.hook( 'wikipage.content' ).add( function ( $c ) { initAll( $c[0] ); } ); | |||
} | |||
}() ); | |||