MediaWiki:All.js
De Inkipedia
Nota: Después de publicar, quizás necesite actualizar la caché de su navegador para ver los cambios.
- Firefox/Safari: Mantenga presionada la tecla Shift mientras pulsa el botón Actualizar, o presiona Ctrl+F5 o Ctrl+R (⌘+R en Mac)
- Google Chrome: presione Ctrl+Shift+R (⌘+Shift+R en Mac)
- Internet Explorer/Edge: mantenga presionada Ctrl mientras pulsa Actualizar, o presione Ctrl+F5
- Opera: Presiona Ctrl+F5.
/* Cualquier JavaScript aquí se cargará para todos los usuarios tanto en el escritorio como en el móvil */ /* strawpoll.me */ mw.loader.load('//splatoonwiki.org/wiki/MediaWiki:StrawpollIntegrator.js?action=raw&ctype=text/javascript') /* strawpoll.com */ mw.loader.load('//splatoonwiki.org/wiki/MediaWiki:StrawpollIntegrator2.js?action=raw&ctype=text/javascript') // ================================================================================ // JS/CSS específicos de la página // ================================================================================ //Comprueba los archivos específicos de la página $(function () { var url = new URL(window.location.href); var action = url.searchParams.get("action") if (action === null || action === "view" || action === "submit") { mw.loader.using("mediawiki.api", function () { var skin = mw.config.get("skin"), page = mw.config.get("wgPageName"), user = mw.config.get("wgUserName"); var pages = [ ['MediaWiki:Common.js/' + page + ".js", "js"], ['MediaWiki:Common.css/' + page + ".css", "css"], ['MediaWiki:' + skin + '.js/' + page + ".js", "js"], ['MediaWiki:' + skin + '.css/' + page + ".css", "css"] ]; if (user != null) pages.push( ['User:' + user + '/common.js/' + page + ".js", "js"], ['User:' + user + '/common.css/' + page + ".css", "css"], ['User:' + user + '/' + skin + '.js/' + page + ".js", "js"], ['User:' + user + '/' + skin + '.css/' + page + ".css", "css"] ); pages.forEach(function (el) { if (el[1] == "js") { if (new URL(window.location).searchParams.get("disable-page-js") != null) return; mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/javascript'); } else { if (new URL(window.location).searchParams.get("disable-page-css") != null) return; mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/css', 'text/css'); } }); }); } }); // ================================================================================ // Función de sustitución del nombre de usuario para [[Template:USERNAME]] // ================================================================================ // Inserta el nombre de usuario en <span class="insertusername"></span>. // Desactivar estableciendo disableUsernameReplace = true. jQuery(function($) { if (typeof(disableUsernameReplace) != 'undefined' && disableUsernameReplace) return; var username = mw.config.get('wgUserName'); if (username == null) return; $('.insertusername').text(username); }); // ================================================================================ // Función de sustitución de Editcount para [[Template:EDITCOUNT]] // ================================================================================ // Inserta el recuento de ediciones en <span class="inserteditcount"></span> jQuery(function($) { var userEditCount = mw.config.get('wgUserEditCount'); if (userEditCount == null) return; $('.inserteditcount').text(userEditCount); }); // ================================================================================ // Función de sustitución de la fecha de registro para [[Template:REGISTRATIONDATE]] // ================================================================================ // Inserta la fecha de registro en <span class="insertregistrationdate"></span> jQuery(function($) { var userRegistrationDate = mw.config.get('wgUserRegistration'); if (userRegistrationDate == null) return; var d = new Date(0); // Sets the date to the epoch d.setUTCMilliseconds(userRegistrationDate); $('.insertregistrationdate').text(d.toLocaleString()); }); /////////////////////////////////////////////////////////////////////////////// // Funciones de la utilidad de programación // /////////////////////////////////////////////////////////////////////////////// // Avanza una marca de tiempo al siguiente múltiplo de 2 horas function advanceDateTime(time) { var ret = new Date(time.getTime()); ret.setMinutes(0); ret.setTime(ret.getTime() + 3600000 * ( ret.getUTCHours() & 1 ? 1 : 2 )); return ret; } // Formatea una marca de tiempo como una cadena en tiempo function formatDateTime(time) { var ret = time.getDate()+ " " + [ "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic." ][time.getMonth()] + " (" + zeroPad(time.getHours(), 2) + ":" + zeroPad(time.getMinutes(), 2) + ")" ; return ret; } // Analiza una cadena de fecha UTC en el formato "MMM dd hh:mm YYYY", el año al final de la cadena es opcional y reemplaza el argumento del año si se proporciona function parseDateTime(text, year) { text = text.split(/[\s:]+/); if(parseInt(text[4]) != NaN && parseInt(text[4]) < 9999 && parseInt(text[4]) >= 1970) year = text[4]; return new Date(Date.UTC( year, { jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11 }[text[0].toLowerCase()], parseInt(text[1]), // Day parseInt(text[2]), // Hours parseInt(text[3]), // Minutes 0, 0 // Seconds, milliseconds )); } // Convierte la última cadena obtenida en un objeto Date function parseFetched(now, text) { var ret = parseDateTime(text, now.getUTCFullYear()); if (now < ret) // Cuentas del límite del año ret.setUTCFullYear(ret.getUTCFullYear() - 1); return ret; } // Convierte una cadena de programación en un objeto function parseSchedule(fetched, text) { var ret = parseDateTime(text, fetched.getUTCFullYear()); if (ret.getTime() < fetched.getTime() - 8640000000) ret.setUTCFullYear(ret.getUTCFullYear() + 1); return ret; } // Calcula el tiempo restante hasta una marca de tiempo dada, como una cadena function timeUntil(now, target) { target = target.getTime() - now.getTime(); target = Math.floor(target % 7200000 / 1000); var seconds = zeroPad(target % 60, 2); var minutes = zeroPad(Math.floor(target / 60) % 60, 2); var hours = Math.floor(target / 3600); return hours + ":" + minutes + ":" + seconds; } // Rellena un número con ceros a la izquierda function zeroPad(number, digits) { number = "" + number; while (number.length < digits) number = "0" + number; return number; } /////////////////////////////////////////////////////////////////////////////// // Clase BattleSchedule // /////////////////////////////////////////////////////////////////////////////// // Mantiene la actualización automática de los elementos del calendario de Ink Battle // Constructor de objetos var BattleSchedule = function() { // Inicializa los campos de la instancia this.lblNow = document.getElementById("battle1"); this.lblNext = document.getElementById("battle2"); this.lblFetched = document.getElementById("battleFetched"); this.prev = false; // Comprobación de errores if (!this.lblFetched) return; // No hay datos sobre el horario // Obtener las marcas de tiempo actuales y las últimas obtenidas var now = new Date(); var fetched = parseFetched(now, this.lblFetched.innerHTML); // Determina la marca de tiempo de las siguientes dos rotaciones this.next = advanceDateTime(fetched); this.later = advanceDateTime(this.next); // Actualiza la visualización inicial this.onTick(now); this.lblFetched.innerHTML = formatDateTime(fetched); // Programa actualizaciones periódicas var that = this; this.timer = setInterval(function() { that.onTick(new Date()); }, 1000); }; // Manipulador de actualizaciones periódicas BattleSchedule.prototype.onTick = function(now) { // Manipulador de actualizaciones periódicas if (now >= this.next && !this.prevNow) { this.prev = true; this.lblNow.innerHTML = "Anterior"; } // Determina cuando la fila "Siguiente" entra en el pasado if (now >= this.later && !this.prevNext) { this.lblNext.innerHTML = "Anterior"; clearInterval(this.timer); return; } // Muestra el tiempo hasta la siguiente rotación this.lblNext.innerHTML = (this.prev ? "Ahora, por otros " : "Siguiente, en ") + timeUntil(now, this.prev ? this.later : this.next) ; }; new BattleSchedule(); /////////////////////////////////////////////////////////////////////////////// // Clase SalmonSchedule // /////////////////////////////////////////////////////////////////////////////// // Mantiene la actualización automática de los elementos del calendario de Salmon Run // Constructor de objetos var SalmonSchedule = function() { // Obteiene las marcas de tiempo actuales y las últimas obtenidas var lblFetched = document.getElementById("salmonFetched"); if (!lblFetched) return; // Sin horario var now = new Date(); var fetched = parseFetched(now, lblFetched.innerHTML); // Inicializa los campos de la instancia this.slots = [ this.parse(document.getElementById("salmon1"), fetched), this.parse(document.getElementById("salmon2"), fetched), ]; // Actualiza la visualización inicial this.onTick(now); lblFetched.innerHTML = formatDateTime(fetched); // Programa actualizaciones periódicas var that = this; this.timer = setInterval(function() { that.onTick(new Date()); }, 1000); }; // Manejador de actualización periódica SalmonSchedule.prototype.onTick = function(now) { // Ciclo a través de las ranuras for (var x = 0; x < this.slots.length; x++) { var slot = this.slots[x]; if (slot.prev) continue; // Saltar esta ranura // Determina cuándo esta ranura debe dejar de actualizarse slot.prev = now >= slot.end; // Actualiza el elemento slot.element.innerHTML = now >= slot.end ? "Anterior" : now >= slot.start ? "Ahora - " + formatDateTime(slot.end) : formatDateTime(slot.start) + " - " + formatDateTime(slot.end) ; } // Desprograma el temporizador if (this.slots[this.slots.length - 1].prev) clearInterval(this.timer); }; // Analiza una única ranura de programación de Salmon Run SalmonSchedule.prototype.parse = function(element, fetched) { var text = element.innerHTML; return { element: element, start: parseSchedule(fetched, text.substring( 0, 12)), end: parseSchedule(fetched, text.substring(15, 27)), prev: false }; } new SalmonSchedule(); /////////////////////////////////////////////////////////////////////////////// // Clase ShopSchedule // /////////////////////////////////////////////////////////////////////////////// // Mantiene la actualización automática de los elementos del calendario de la tienda SplatNet 2 // Constructor de objetos var ShopSchedule = function() { var lblFetched = document.getElementById("shopFetched"); if (!lblFetched) return; // Sin horario // Obtiene las marcas de tiempo actuales y las últimas obtenidas var now = new Date(); var fetched = parseFetched(now, lblFetched.innerHTML); // Actualiza la visualización inicial lblFetched.innerHTML = formatDateTime(fetched); }; new ShopSchedule(); /////////////////////////////////////////////////////////////////////////////// // Clase SplatfestSchedule // /////////////////////////////////////////////////////////////////////////////// // Mantiene la actualización automática de los elementos del calendario del Splatfest // Constructor de objetos var SplatfestSchedule = function() { // Inicializa los campos de la instancia var now = new Date(); this.slots = [ this.parse(document.getElementById("splatfest1"), now), this.parse(document.getElementById("splatfest2"), now), this.parse(document.getElementById("splatfest3"), now) ]; // Actualiza la visualización inicial this.onTick(now); // Programa actualizaciones periódicas var that = this; this.timer = setInterval(function() { that.onTick(new Date()); }, 1000); }; // Manipulador de actualizaciones periódicas SplatfestSchedule.prototype.onTick = function(now) { // Ciclo a través de las ranuras for (var x = 0; x < this.slots.length; x++) { var slot = this.slots[x]; if (slot.prev) continue; // Saltar esta ranura // Determina cuando esta ranura debe dejar de actualizarse slot.prev = now >= slot.end; // Actualiza el elemento slot.element.innerHTML = now >= slot.end ? "Concluido" : now >= slot.start ? "Ahora - " + formatDateTime(slot.end) : formatDateTime(slot.start) ; } // Desprograma el temporizador if (this.slots[this.slots.length - 1].prev) clearInterval(this.timer); }; // Analiza una sola ranura de Splatfest schdule SplatfestSchedule.prototype.parse = function(element, now) { // Comprobación de errores if (!element) return { prev: true }; // Determina la hora actual y las marcas de tiempo de inicio y fin var start = parseDateTime(element.innerHTML, now.getUTCFullYear()); return { element: element, start: start, end: new Date(start.getTime() + 172800000), prev: false }; }; new SplatfestSchedule(); // ================================================================================ // MediaLoader - Evitar que el audio se cargue hasta que se haga clic // Version 2 (19.04.2020) // ================================================================================ window.MediaLoader = {}; window.MediaLoader.FileCache = {}; function MLGetFileFromName(name){ return new Promise(function(k,no){ if(window.MediaLoader.FileCache[name] == null){ new mw.Api().get({ "action": "parse", "format": "json", "text": "[["+name+"]]", "prop": "text", "contentmodel": "wikitext" }).then(function(file){ var filetext = $($.parseHTML(file.parse.text["*"])).find('p').html(); window.MediaLoader.FileCache[name] = filetext; k(filetext); },no); } else k(window.MediaLoader.FileCache[name]); }) } mw.loader.using("mediawiki.api", function(){ $(".MediaLoader").each(function(){ $(this).data("state", "unloaded"); var children = $(this).children(); if(children.length < 1){ console.error("[MediaLoader] Error P1"); return; } var child = $(children[0]); child.find(".MediaLoader-text").click(function(){ var parent = $(this).parent().parent(); try{ if(parent.data("state") == "unloaded"){ parent.data("state", "busy"); $(this).text("Loading..."); MLGetFileFromName(parent.data("file")).then(function(filetext){ parent.find(".MediaLoader-file").html(filetext); parent.find(".MediaLoader-text").text("Unload "+parent.data("name")); parent.data("state", "loaded"); }, console.error) } else if(parent.data("state") == "loaded"){ parent.find(".MediaLoader-file").html(""); $(this).text("Load "+parent.data("name")); parent.data("state", "unloaded"); } } catch(ex){ console.error(ex); parent.data("state", "error"); $(this).text("An unexpected error has occured"); parent.find(".MediaLoader-file").html("<a></a>"); parent.find(".MediaLoader-file").children("a").attr("href", "//splatoonwiki.org/wiki/"+parent.data("file")) parent.find(".MediaLoader-file").children("a").text(parent.data("name")) $(this).css("cursor", ""); } }) child.find(".MediaLoader-text").text("Load "+$(this).data("name")); child.find(".MediaLoader-text").addClass("noselect"); child.find(".MediaLoader-text").css("cursor", "pointer"); child.find(".MediaLoader-file").addClass("noselect"); }) $(".MediaLoadAll").each(function(){ var children = $(this).children(); if(children.length < 2){ console.error("[MediaLoadAll] Error P1"); return; } children.click(function(){ var parent = $(this).parent(); try{ var load = $(this).hasClass("MediaLoadAll-load"); $(parent.data("group") != "{{{group}}}" ? '.MediaLoader[data-group="'+parent.data("group")+'"]' : ".MediaLoader").each(function(){ if(($(this).data("state") == "unloaded" && load) || ($(this).data("state") == "loaded" && !load)) $(this).find(".MediaLoader-text").click(); }) } catch(ex){ console.error(ex); $(this).text("An unexpected error has occured"); $(this).css("cursor", ""); } }) $(this).css("display", "") children.filter(".MediaLoadAll-load").text("Load all "+$(this).data("name")); children.filter(".MediaLoadAll-unload").text("Unload all "+$(this).data("name")); children.addClass("noselect"); children.css("cursor", "pointer"); }) }) // ================================================================================ // Countdowns // ================================================================================ // Credits go to AbelToy, Guy Perfect, Espyo for the countdown code. // List of countdowns on the current page var countdowns = []; // Converts from time to a clean time info structure function timeToStruct(time) { var passed = time < 0; //Has the moment passed? // Parse time fields from the number time = Math.floor(time / 1000); var secs = ("00" + (time % 60)).slice(-2); time = Math.floor(time / 60); var mins = ("00" + (time % 60)).slice(-2); time = Math.floor(time / 60); var hours = ("00" + (time % 24)).slice(-2); time = Math.floor(time / 24); // Construct the string representation return { d: time, h: hours, m: mins, s: secs, p: passed }; } // Gets the time remaining until the next stage rotation function getStageCountdown(now) { var hour = Math.floor(now / 3600000) % 24 + 2; // Add 2 for UTC bias var now = hour * 3600000 + now % 3600000; // Current adjusted hour var target = (hour + 4 & -4) * 3600000; // Target hour return target - now; } function tickCountdowns() { var now = Date.now(); for(var c = 0; c < countdowns.length; c++){ var diff = 0; if(countdowns[c].stage) { diff = timeToStruct(getStageCountdown(now)); } else { diff = timeToStruct(countdowns[c].time - now); } if(diff.p && diff.d < -1) { // Over 24 hours passed countdowns[c].span.innerHTML = countdowns[c].doneMessage; } else if(diff.p){ // 24 hours haven't passed yet countdowns[c].span.innerHTML = countdowns[c].ongoingMessage; } else { // The time hasn't come yet countdowns[c].span.innerHTML = diff.d + "d :" + diff.h + "h :" + diff.m + "m :" + diff.s + "s"; } } } // Returns the info from a countdown span on the page. function getCountdownInfo(countdown, stage) { var time = null; var ongoingMessage = ""; var doneMessage = ""; if(!stage) { // Format is "<day> <hour>|<24 hour msg>|<afterwards msg>" var parts = countdown.innerHTML.split("|"); doneMessage = (parts.length >= 3) ? parts[2] : parts[1]; ongoingMessage = parts[1]; var timeParts = parts[0].split(/[ \n]/); var date = timeParts[0].split("/"); var hour = timeParts[1].split(":"); time = Date.UTC(date[0], date[1] - 1, date[2], hour[0], hour[1]); } countdowns.push( { span: countdown, stage: stage, time: time, ongoingMessage: ongoingMessage, doneMessage: doneMessage } ); // The spans start hidden and with the info // Delete the info and show the span countdown.style.display = "inline"; countdown.innerHTML = ""; } // Finds countdown spans on the document and sets up the countdowns function setupCountdowns() { var stageCountdowns = document.getElementsByClassName("stageCountdown"); for(var sc = 0; sc < stageCountdowns.length; sc++) { getCountdownInfo(stageCountdowns[sc], true); } var countdowns = document.getElementsByClassName("countdown"); for(var c = 0; c < countdowns.length; c++) { getCountdownInfo(countdowns[c], false); } setInterval(tickCountdowns, 1000); } setupCountdowns(); // ================================================================================ // 3D/2D Viewer // ================================================================================ // 3D model viewer var viewer3d = { dragging: null, draggingFrameX: 0, draggingFrameY: 0, viewers: [], frameThresholdX: 10, frameThresholdY: 128, realMod: function(x, y) { return ((x % y) + y) % y; }, init: function() { $('.viewer-3d').each(viewer3d.bind); $(document).mouseup(viewer3d.release); $(document).mousemove(viewer3d.move); }, bind: function() { var v = $(this); var num = viewer3d.viewers.length; var allModels = []; var modelID = 0; var viewerSize = 0; while(true) { var modelMap = v.find('.viewer-3d-map-' + modelID); var urlNode = v.find('.viewer-3d-url-' + modelID); if(!modelMap.length || !urlNode.length) break; var url = $('<div/>').html(urlNode.text()).text(); var framesS = $('<div/>').html(modelMap.text()).text().replace(/^\s+|\s+$/g).split(/,/g); var frameMap = []; var heightMap = []; var leftCropMap = []; var totalW = parseInt(framesS[0]); var maxFrameW = parseInt(framesS[1]); var totalH = parseInt(framesS[2]); var verticalSteps = parseInt(framesS[3]); var midVertical = Math.floor(verticalSteps / 2); for(var f = 4; f < framesS.length; f += 3) { frameMap.push(parseInt(framesS[f])); heightMap.push(parseInt(framesS[f + 1])); leftCropMap.push(parseInt(framesS[f + 2])); } allModels.push({ imageURL: url, map: frameMap, cropMap: leftCropMap, totalWidth: totalW, totalHeight: totalH, maxFrameWidth: maxFrameW, xStep: verticalSteps }); viewerSize = Math.max(viewerSize, totalH, maxFrameW); modelID++; } if(!modelID) return; var overlayNode = $('<div class="viewer-3d-overlay"></div>'); var frameN = v.find('.viewer-3d-frame'); v.find('img').detach(); var klasses = v.attr('class').split(/ /g); var startFrame = 0; for(var k in klasses) { if(klasses[k].substr(0, 11) == 'startframe-') { startFrame = Math.max(0, parseInt(klasses[k].substr(11))); } } var viewer = { node: v, frameX: startFrame, frameY: midVertical, models: allModels, currentModel: -1, frameNode: frameN, width: viewerSize, height: viewerSize, mouseX: 0, mouseY: 0, overlay: overlayNode }; viewer3d.viewers.push(viewer); v.hover(viewer3d.hover, viewer3d.unhover).mousedown(viewer3d.drag).append(overlayNode).attr('data-id', num).css({ width: viewer.width + 'px', height: viewer.height + 'px' }); frameN.mousedown(viewer3d.drag).attr('data-id', num).css('height', viewer.height + 'px'); viewer3d.changeVersion(viewer, 0); }, getCurrentModel: function(v) { return v.models[v.currentModel]; }, changeVersion: function(v, version) { version = Math.max(0, Math.min(v.models.length - 1, version)); if(v.currentModel == version) return; v.currentModel = version; v.frameNode.css('background', 'url(' + viewer3d.getCurrentModel(v).imageURL + ') top left no-repeat'); viewer3d.display(v, v.frameX, v.frameY); }, hover: function(e) { var v = viewer3d.getViewer(this); if(viewer3d.dragging != v) { v.overlay.animate({'opacity': '1'}, 'fast'); } }, unhover: function(e) { var v = viewer3d.getViewer(this); if(viewer3d.dragging != v) { v.overlay.animate({'opacity': '0.5'}, 'fast'); } }, drag: function(e) { var v = viewer3d.getViewer(this); v.mouseX = e.pageX; v.mouseY = e.pageY; viewer3d.dragging = v; draggingFrameX = v.frameX; draggingFrameY = v.frameY; return false; }, release: function() { var v = viewer3d.dragging; viewer3d.dragging = null; if(v != null) { v.frameX = viewer3d.draggingFrameX; v.frameY = viewer3d.draggingFrameY; v.overlay.animate({'opacity': '0.5'}, 'fast'); } viewer3d.draggingFrameX = 0; viewer3d.draggingFrameY = 0; }, getViewer: function(node) { return viewer3d.viewers[parseInt($(node).attr('data-id'))]; }, display: function(v, frameX, frameY) { var model = viewer3d.getCurrentModel(v); var frameID = viewer3d.realMod(frameX * model.xStep + frameY, model.map.length); var frameOffset = model.map[frameID]; var frameWidth = 0; if(frameID == model.map.length - 1) { frameWidth = model.totalWidth - frameOffset; } else { frameWidth = model.map[frameID + 1] - frameOffset; } v.frameNode.css({ backgroundPosition: (-frameOffset - frameID) + 'px 0px', left: Math.round((v.width - model.maxFrameWidth) / 2.0 + model.cropMap[frameID]) + 'px', top: Math.round((v.height - model.totalHeight) / 2) + 'px', width: frameWidth + 'px', height: model.totalHeight + 'px' }); }, move: function(e) { if(viewer3d.dragging == null) { return; } var v = viewer3d.dragging; var model = viewer3d.getCurrentModel(v); var mouseDeltaX = e.pageX - v.mouseX; var mouseDeltaY = e.pageY - v.mouseY; var frameDeltaX = Math.round(mouseDeltaX / viewer3d.frameThresholdX); var frameDeltaY = -Math.round(mouseDeltaY / viewer3d.frameThresholdY); viewer3d.draggingFrameX = v.frameX + frameDeltaX; viewer3d.draggingFrameY = Math.max(0, Math.min(model.xStep - 1, v.frameY + frameDeltaY)); viewer3d.display(v, viewer3d.draggingFrameX, viewer3d.draggingFrameY); } }; $(viewer3d.init); var selector3d = { bind: function() { var viewer = viewer3d.getViewer($(this).find('.viewer-3d')); var keepGoing = true; var modelVariant = 0; var selector; while(keepGoing) { selector = $(this).find('.selector-' + modelVariant); if(selector.length) { selector.attr('data-variant', modelVariant).click(function() { viewer3d.changeVersion(viewer, parseInt($(this).attr('data-variant'))); return false; }); } modelVariant++; keepGoing = selector.length; } }, init: function() { $('.viewer-3d-multi, .viewer-3d-container').each(selector3d.bind); } }; $(selector3d.init); // Code to get 3D viewer drag working on touch devices // Source: http://www.jquery4u.com/mobile/jquery-add-dragtouch-support-ipad/ $.fn.addTouch = function(){ this.each(function(i,el){ $(el).bind('touchstart touchmove touchend touchcancel',function(){ //we pass the original event object because the jQuery event //object is normalized to w3c specs and does not provide the TouchList handleTouch(event); }); }); var handleTouch = function(event) { var touches = event.changedTouches, first = touches[0], type = ''; switch(event.type) { case 'touchstart': type = 'mousedown'; break; case 'touchmove': type = 'mousemove'; event.preventDefault(); break; case 'touchend': type = 'mouseup'; break; default: return; } var simulatedEvent = document.createEvent('MouseEvent'); simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0/*left*/, null); first.target.dispatchEvent(simulatedEvent); }; }; $('.viewer-3d').addTouch(); // End 3D viewer touch device code