JSFiddle
https://jsfiddle.net/wz9ghpnc/
Issue
Using: latest jQuery
Why this lagging to much on mobile devices? I'm really done with trying to optimize this.
How can I apply something like debounce / throttling? I tried to add setTimeout to move() function with 50 ms delay but that was laggy too.
Moving on mobile is so laggy... animations are too so laggy...
let $state = {
afterDown: {
clientX: 0,
scrollLeft: 0,
},
afterUp: {
clientX: 0,
scrollLeft: 0,
},
dreamedScrollLeft: 0,
isDown: false,
};
let centering = false;
let duration = 250;
let messages = false;
function message () {
if (messages === true) {
console.log('%c addScroll ', 'background: #000; color: #fff;', ...arguments);
}
}
// Events
function down (parent) {
return (event) => {
message('down');
if ($state.isDown === false) {
$state.afterDown.clientX = event.originalEvent.touches ? event.originalEvent.touches[0].clientX : event.clientX;
$state.afterDown.scrollLeft = $(parent).scrollLeft();
$(parent).stop();
$state.isDown = true;
}
};
}
function leave (parent) {
return () => {
message('leave');
if ($state.isDown === true) {
$(parent).removeClass('scroll-moving');
$state.isDown = false;
}
};
}
function move (parent) {
return (event) => {
message('move');
if ($state.isDown === true) {
$(parent).addClass('scroll-moving');
const clientX = event.originalEvent.touches ? event.originalEvent.touches[0].clientX : event.clientX;
$(parent).scrollLeft($state.afterDown.clientX + $state.afterDown.scrollLeft - clientX);
}
};
}
function scroll (parent) {
return () => {
message('scroll');
if (centering === true) {
if ($(parent).scrollLeft() === $state.dreamedScrollLeft) {
message('after scroll centering');
if (centering === true) {
if ($(parent).scrollLeft() === $state.dreamedScrollLeft) {
message('after scroll centering');
}
}
}
}
};
}
function up (parent) {
return (event) => {
message('up');
if ($state.isDown === true) {
$(parent).removeClass('scroll-moving');
$state.afterUp.clientX = event.originalEvent.changedTouches ? event.originalEvent.changedTouches[0].clientX : event.clientX;
$state.afterUp.scrollLeft = $(parent).scrollLeft();
if ($state.afterDown.clientX > $state.afterUp.clientX) {
message('👉 moving right');
$state.dreamedScrollLeft = $(parent).scrollLeft() + ($state.afterDown.clientX - $state.afterUp.clientX);
}
if ($state.afterUp.clientX > $state.afterDown.clientX) {
message('👈 moving left');
$state.dreamedScrollLeft = $(parent).scrollLeft() - ($state.afterUp.clientX - $state.afterDown.clientX);
}
$(parent).animate({ scrollLeft: $state.dreamedScrollLeft, }, duration, 'linear');
message($state.afterDown, $state.afterUp, $state.dreamedScrollLeft);
$state.isDown = false;
}
};
}
$.fn.extend({
addScroll: function (i) {
if (i.centering) {
centering = i.centering;
}
if (i.duration) {
duration = i.duration;
}
if (i.messages) {
messages = i.messages;
}
$(this).css('overflow', 'hidden');
$(this).bind('mousedown touchstart', down(this));
$(this).bind('mouseleave', leave(this));
$(this).bind('mousemove touchmove', move(this));
$(this).bind('scroll', scroll(this));
$(this).bind('mouseup touchend', up(this));
},
});
Related
I have two independent functions one to build a carousel and one to detect swipe Left or Right.
I cannot seem to find a way to use the function which navigates through the carousel into my Swipe Left or Right function (or vice versa).
I am at the end of my JS coding limits and struggling.
Here is the except:
this.doSomething = (event) => {
if (this.whatGestureDirection() == 'L') {
hpCarousel.move(-1);
} else {
hpCarousel.move(1);
}
}
I have tried alternatives; having google'd around there is a bind method. However, this is a bust too.
this.doSomething = (event) => {
if (this.whatGestureDirection() == 'L') {
let bindTheFunc;
bindTheFunc.bind(hpCarousel.move(-1));
return bindTheFunc;
} else {
let bindTheFunc;
bindTheFunc.bind(hpCarousel.move(1));
return bindTheFunc;
}
}
window.addEventListener('load', () => {
const hpCarousel = new carousel('area', 1);
const hpCarouselSwipe = new getSwipeX({elementId: 'homepage_carousel_wrapper'});
})
function getSwipeX({elementId}) {
this.e = document.getElementsByClassName(elementId)[0];
this.initialPosition = 0;
this.lastPosition = 0;
this.getTouchStart = (event) => {
event.preventDefault();
if (window.PointerEvent) {
this.e.setPointerCapture(event.pointerId);
}
return this.initalTouchPos = this.getGesturePoint(event);
}
this.getTouchMove = (event) => {
event.preventDefault();
return this.lastPosition = this.getGesturePoint(event);
}
this.getTouchEnd = (event) => {
event.preventDefault();
if (window.PointerEvent) {
this.e.releasePointerCapture(event.pointerId);
}
this.doSomething();
this.initialPosition = 0;
}
this.getGesturePoint = (event) => {
this.point = event.pageX
return this.point;
}
this.whatGestureDirection = (event) => {
const diffInPosition = this.initalTouchPos - this.lastPosition;
return (Math.sign(diffInPosition) > 0 ) ? `L` : `R`;
}
this.doSomething = (event) => {
if (this.whatGestureDirection() == 'L') {
**hpCarousel.move(-1);**
} else {
**hpCarousel.move(1);**
}
}
if (window.PointerEvent) {
this.e.addEventListener('pointerdown', this.getTouchStart, true);
this.e.addEventListener('pointermove', this.getTouchMove, true);
this.e.addEventListener('pointerup', this.getTouchEnd, true);
this.e.addEventListener('pointercancel', this.getTouchEnd, true);
}
}
I have a carousel script which a SO user kindly helped me with last week. It works great.
function carousel(id, index) {
this.slideIndex = index;
let carousel = document.getElementById(id);
this.slides = [...document.getElementsByClassName('homepage_carousel')];
let prev = carousel.getElementsByClassName('prev')[0];
let next = carousel.getElementsByClassName('next')[0];
prev.addEventListener('click', () => {
this.move(-1);
});
next.addEventListener('click', () => {
this.move(1);
});
this.hideAll = () => {
this.slides.forEach((slide) => {
slide.style.display = 'none';
});
};
this.show = () => {
this.hideAll();
this.slides[this.slideIndex - 1].style.display = 'flex';
}
this.move = (amount) => {
this.slideIndex += amount;
this.slideIndex = (this.slideIndex > this.slides.length) ? 1 : (this.slideIndex < 1) ? this.slides.length : this.slideIndex;
this.show();
}
this.show();
}
you can pass hpCarousel into hpCarouselSwipe as an argument.
window.addEventListener('load', () => {
const hpCarousel = new carousel('area', 1);
const hpCarouselSwipe = new getSwipeX({elementId: 'homepage_carousel_wrapper', carousel:hpCarousel});
})
function getSwipeX({elementId,carousel}) {
...
this.hpCarousel = carousel
}
And then later:
this.doSomething = (event) => {
if (this.whatGestureDirection() == 'L') {
this.hpCarousel.move(-1);
} else {
this.hpCarousel.move(1);
}
}
I did not test it, but it should work. You can pass a function into another function.
I am testing twoway-motion.js on Aframe, by providing a simple way to navigate specifically without a device orientation permission from a mobile phone.
please check this glitch page for details: https://glitch.com/~scrawny-efraasia
also please see twoway-motion.js by #flowerio
AFRAME.registerComponent('twoway-motion', {
schema: {
speed: { type: "number", default: 40 },
threshold: { type: "number", default: -40 },
nonMobileLoad: { type: "boolean", default: false },
removeCheckpoints: {type: "boolean", default: true },
chatty: {type: "boolean", default: true }
},
init: function () {
var twowaymotion = document.querySelector("[camera]").components["twoway-motion"];
twowaymotion.componentName = "twoway-motion";
report = function(text) {
if (twowaymotion.data.chatty) {
console.log(twowaymotion.componentName, ":", text);
}
}
report("init.");
// report("asked to load with speed=", this.data.speed);
if (!AFRAME.utils.device.isMobile() && this.data.nonMobileLoad === false) {
// this is only for mobile devices.
//document.querySelector("[camera]").removeAttribute("twoway-motion");
report("Retired. Will only work on mobile.");
return;
} else {
if (this.data.nonMobileLoad === true) {
report("Loading on non-mobile platform.");
}
}
if (this.el.components["wasd-controls"] === undefined) {
this.el.setAttribute("wasd-controls", "true");
report("Installing wasd-controls.");
}
this.el.components["wasd-controls"].data.acceleration = this.data.speed;
// two-way hides checkpoint-controls by default.
if (this.data.removeCheckpoints) {
if (this.el.components["checkpoint-controls"] !== undefined) {
var checkpoints = document.querySelectorAll("[checkpoint]");
for (var cp = 0; cp < checkpoints.length; cp++) {
checkpoints[cp].setAttribute("visible", false);
}
}
}
this.el.removeAttribute("universal-controls");
if (this.el.components["look-controls"] === undefined) {
this.el.setAttribute("look-controls", "true");
}
var cur = document.querySelector("[cursor]");
if (cur !== null) {
console.log(this.componentName, ": found a cursor.");
this.cur = cur;
//this.curcolor = cur.getAttribute("material").color;
this.curcolor = cur.getAttribute("color");
} else {
console.log(this.componentName, ": didn't find a cursor.");
}
var canvas = document.querySelector(".a-canvas");
canvas.addEventListener("mousedown", function (e) {
report("mousedown", e);
twowaymotion.touching = true;
this.touchTime = new Date().getTime();
});
canvas.addEventListener("mouseup", function (e) {
report("mouseup", e);
twowaymotion.touching = false;
});
canvas.addEventListener("touchstart", function (e) {
this.touch = e;
report("touches.length: ", e.touches.length);
if (e.touches.length > 1) {
report("multitouch: doing nothing");
} else {
report("touchstart", e);
twowaymotion.touching = true;
}
});
canvas.addEventListener("touchend", function () {
console.log(this.componentName, " touchend");
twowaymotion.touching = false;
});
},
update: function() {
if (this.el.components["twoway-controls"] !== undefined) {
this.el.components["wasd-controls"].data.acceleration = this.el.components["wasd-controls"].data.speed;
}
},
tick: function () {
if (!AFRAME.utils.device.isMobile() && this.data.nonMobileLoad === false) {
// this is only for mobile devices, unless you ask for it.
return;
}
if (!this.isPlaying) {
return;
}
var cam = this.el;
var camrot = cam.getAttribute("rotation");
if (camrot.x < this.data.threshold) {
// we are looking down
if (this.cur !== null && this.cur !== undefined) {
this.cur.setAttribute("material", "color", "orange");
}
if (this.touching === true) {
cam.components["wasd-controls"].keys["ArrowDown"] = true;
} else {
cam.components["wasd-controls"].keys["ArrowDown"] = false;
cam.components["wasd-controls"].keys["ArrowUp"] = false;
}
} else {
// we are looking forward or up
if (this.cur !== null && this.cur !== undefined) {
this.cur.setAttribute("material", "color", this.curcolor);
}
if (this.touching === true) {
cam.components["wasd-controls"].keys["ArrowUp"] = true;
} else {
cam.components["wasd-controls"].keys["ArrowDown"] = false;
cam.components["wasd-controls"].keys["ArrowUp"] = false;
}
}
},
pause: function () {
// we get isPlaying automatically from A-Frame
},
play: function () {
// we get isPlaying automatically from A-Frame
},
remove: function () {
if (this.el.components["wasd-controls"] === undefined) {
this.el.removeAttribute("wasd-controls");
}
} });
Since device orientation permission is not granted on mobile phones, move backward is not working, also, when the audience tries to rotate in a different direction by touching the screen or sliding on screen, it still functions as move forward.
from what I imagine, if there is a simple edit, if the audience touches the screen more than 2 seconds, it start to move forward, if audience just rotate, it will not move forward, since when you slide or touch on the screen to rotate the touching time might not be so long as 2 seconds...
this is the easiest solution that I can imagine under the restriction of without device orientation permission.
or is there any other better way to divide rotate and move forward by touching screen regarding touching time?
Thks!!!!!
I've been trying to get mithril touch to work using a good code on github:
https://gist.github.com/webcss/debc7b60451f2ad2af41
import m from 'mithril'
/*****************************************
/* DOM touch support module
/*****************************************/
if (!window.CustomEvent) {
window.CustomEvent = function (event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
(function(document) {
var TAPTRESHOLD = 200, // time within a double tap should have happend
TAPPRECISION = 60 / 2, // distance to identify a swipe gesture
touch = { },
tapCount = 0, // counts the number of touchstart events
tapTimer = 0, // timer to detect double tap
isTouchSwipe = false, // set to true whenever
absolute = Math.abs,
touchSupported = 'ontouchstart' in window;
function parentIfText (node) {
return 'tagName' in node ? node : node.parentNode;
}
function dispatchEvent(type, touch) {
if(touchSupported) {
touch.originalEvent.preventDefault();
touch.originalEvent.stopImmediatePropagation();
}
var event = new CustomEvent(type, {
detail: touch,
bubbles: true,
cancelable: true
});
touch.target.dispatchEvent(event);
console.log(type);
touch = { };
tapCount = 0;
return event;
}
function touchStart(e) {
if( !touchSupported || e.touches.length === 1) {
var coords = e.targetTouches ? e.targetTouches[0] : e;
touch = {
originalEvent: e,
target: parentIfText(e.target),
x1: coords.pageX,
y1: coords.pageY,
x2: coords.pageX,
y2: coords.pageY
};
isTouchSwipe = false;
tapCount++;
if (!e.button || e.button === 1) {
clearTimeout(tapTimer);
tapTimer = setTimeout(function() {
if(absolute(touch.x2 - touch.x1) < TAPPRECISION &&
absolute(touch.y2 - touch.y2) < TAPPRECISION &&
!isTouchSwipe) {
dispatchEvent((tapCount===2)? 'dbltap' : 'tap', touch);
clearTimeout(tapTimer);
}
tapCount = 0;
}, TAPTRESHOLD);
}
}
}
function touchMove(e) {
var coords = e.changedTouches ? e.changedTouches[0] : e;
isTouchSwipe = true;
touch.x2 = coords.pageX;
touch.y2 = coords.pageY;
/* the following is obsolete since at least chrome handles this
// if movement is detected within 200ms from start, preventDefault to preserve browser scroll etc.
// if (touch.target &&
// (absolute(touch.y2 - touch.y1) <= TAPPRECISION ||
// absolute(touch.x2 - touch.x1) <= TAPPRECISION)
// ) {
// e.preventDefault();
// touchCancel(e);
// }
*/
}
function touchCancel(e) {
touch = {};
tapCount = 0;
isTouchSwipe = false;
}
function touchEnd(e) {
var distX = touch.x2 - touch.x1,
distY = touch.y2 - touch.y1,
absX = absolute(distX),
absY = absolute(distY);
// use setTimeout here to register swipe over tap correctly,
// otherwise a tap would be fired immediatly after a swipe
setTimeout(function() {
isTouchSwipe = false;
},0);
// if there was swipe movement, resolve the direction of swipe
if(absX || absY) {
if(absX > absY) {
dispatchEvent((distX<0)? 'swipeleft': 'swiperight', touch);
} else {
dispatchEvent((distY<0)? 'swipeup': 'swipedown', touch);
}
}
}
document.addEventListener(touchSupported ? 'touchstart' : 'mousedown', touchStart, false);
document.addEventListener(touchSupported ? 'touchmove' : 'mousemove', touchMove, false);
document.addEventListener(touchSupported ? 'touchend' : 'mouseup', touchEnd, false);
// on touch devices, the taphold complies with contextmenu
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
dispatchEvent('taphold', {
originalEvent: e,
target: parentIfText(e.target)
});
}, false);
if (touchSupported) {
document.addEventListener('touchcancel', touchCancel, false);
}
}(window.document));
m.touchHelper = function(options) {
return function(element, initialized, context) {
if (!initialized) {
Object.keys(options).forEach(function(touchType) {
element.addEventListener(touchType, options[touchType], false);
});
context.onunload = function() {
Object.keys(options).forEach(function(touchType) {
element.removeEventListener(touchType, options[touchType], false);
});
};
}
};
};
The only thing I've added is import m from 'mithril'.
Launching the app I can see that everything is registering as should through the console, however, in my main code I want to use that touch data:
const app = {
view: vnode => {
return m('.main', {config: m.touchHelper({ 'tap': consoleLog})
}
}
function consoleLog() {
console.log('Triggered')
}
However, this function is not triggered.
I don't know how much Mithril has changed since February 2017 (I have only recently picked up Mithril), but that m.touchHelper seems weird. What are element, initialized and context? Why is a function returned; is it ever called?
Using m.touchHelper with the config attribute seems also weird. When you do this:
return m('.main', {config: m.touchHelper({'tap': consoleLog})})
the resulting DOM element looks like this:
<div config="function(element, initialized, context) {
if (!initialized) {
Object.keys(options).forEach(function(touchType) {
element.addEventListener(touchType, options[touchType], false);
});
context.onunload = function() {
Object.keys(options).forEach(function(touchType) {
element.removeEventListener(touchType, options[touchType], false);
});
};
}
}" class="main"></div>
See JSFiddle with your code. (Note that I added missing }) to the end of app's view method.)
I would change m.touchHelper to something like this:
m.touchHelper = function(vnode, options) {
if (vnode.state.initialized) return;
vnode.state.initialized = true;
Object.keys(options).forEach(function(touchType) {
vnode.dom.addEventListener(touchType, options[touchType], false);
});
// Note that I removed the `context.unload` part as I'm unsure what its purpose was
};
And call it in a component's oncreate lifecycle method:
const app = {
oncreate(vnode) {
m.touchHelper(vnode, {'tap': consoleLog});
},
view: vnode => {
return m('.main');
}
};
Then it seems to work. See JSFiddle with updated code.
Before I get into details, please take a look at the live example where this problem occurs - http://jsfiddle.net/66HFU/ (Script code at the bottom of this post)
Now if you would click on any image at last row, it would display these. However if you would click on upper row images, below row images are shown.
Further investigation shows that for somewhat reason the letter called function selector elements only get binded with event listener while the firstly called functions selector elements do not.
So, I would like to know is there are any ways to make the function call independent so the latter function call does not override first one (if that would fix the problem, of-course)?
The place where the event function gets bind on the element can be found in function f_AddEvents first lines.
And the the main function calls that are used to initialize Light Box is at the bottom of the code like this:
LightBox.init({
selector: "[data-simplbox='demo1']",
boxId: "simplbox"
});
LightBox.init({
selector: "[data-simplbox='demo2']",
boxId: "simplbox",
imageLoadStart: activityIndicatorOn,
imageLoadEnd: activityIndicatorOff
});
All code:
;(function (window, document, undefined) {
var docElem = document.documentElement;
var DomM = (function() {
var f_ToDOMStyle = function (p_Style) {
return p_Style.replace(/\-[a-z]/g, function (p_Style) {
return p_Style.charAt(1).toUpperCase();
});
};
return {
event: {
set: function (p_Element, p_Events, p_Function) {
var i = 0,
j = 0;
p_Events = p_Events.split(" ");
if (!p_Element.length) {
for (i = 0; i < p_Events.length; i++) {
p_Element.addEventListener(p_Events[i], p_Function, false);
}
} else {
for (i = 0; i < p_Element.length; i++) {
for (j = 0; j < p_Events.length; j++) {
p_Element[i].addEventListener(p_Events[j], p_Function, false);
}
}
}
}
},
css: {
set: function (p_Element, p_Style) {
var j;
if (!p_Element.length) {
for (j in p_Style) {
if (p_Style.hasOwnProperty(j)) {
j = f_ToDOMStyle(j);
p_Element.style[j] = p_Style[j];
}
}
} else {
for (var i = 0; i < p_Element.length; i++) {
for (j in p_Style) {
if (p_Style.hasOwnProperty(j)) {
j = f_ToDOMStyle(j);
p_Element[i].style[j] = p_Style[j];
}
}
}
}
}
}
};
}());
var _LightBox = {
f_MergeObjects: function (p_Original, p_Updates) {
for (var i in p_Updates) {
if (p_Updates.hasOwnProperty(i)) {
p_Original[i] = p_Updates[i];
}
}
return p_Original;
},
f_isFunction: function (p_Function) {
return !!(p_Function && p_Function.constructor && p_Function.call && p_Function.apply);
},
f_Initialize: function (p_Options) {
var base = this;
base.m_Options = base.f_MergeObjects(_LightBox.options, p_Options || {});
base.m_Elements = document.querySelectorAll(base.m_Options.selector);
base.m_ElementsLength = base.m_Elements.length - 1;
base.m_Body = document.getElementsByTagName("body")[0];
base.m_CurrentImageElement = false;
base.m_CurrentImageNumber = 0;
base.m_Direction = 1;
base.m_InProgress = false;
base.m_InstalledImageBox = false;
console.log(base.m_Elements);
// Check if hardware acceleration is supported and check if touch is enabled.
base.f_CheckBrowser();
// Adds events.
base.f_AddEvents();
},
f_CheckBrowser: function () {
var base = this,
isTouch = "ontouchstart" in window || window.navigator.msMaxTouchPoints || navigator.maxTouchPoints || false,
vendors = ["ms", "O", "Moz", "Webkit", "Khtml"],
rootStyle = docElem.style,
hardwareAccelerated = false;
if ("transform" in rootStyle) {
hardwareAccelerated = true;
} else {
while (vendors.length) {
if (vendors.pop() + "Transform" in rootStyle) {
hardwareAccelerated = true;
}
}
}
base.browser = {
"isHardwareAccelerated": hardwareAccelerated,
"isTouch": isTouch
};
},
f_AddEvents: function () {
var base = this;
// Add open image event on images.
for (var i = 0; i < base.m_Elements.length; i++) {
(function (i) {
base.m_Elements[i].addEventListener("click", function (event) {
event.preventDefault();
console.log(base.m_Elements[i]);
if (base.f_isFunction(base.m_Options.onImageStart)) {
base.m_Options.onImageStart();
}
base.f_OpenImage(i);
}, false);
})(i);
}
// Resize event for window.
window.addEventListener("resize", function (event) {
event.preventDefault();
base.f_SetImage();
}, false);
// Add keyboard support.
if (base.m_Options.enableKeyboard) {
var keyBoard = {
left: 37,
right: 39,
esc: 27
};
window.addEventListener("keydown", function (event) {
event.preventDefault();
if (base.m_CurrentImageElement) {
if (base.m_InProgress) {
return false;
}
switch (event.keyCode) {
case keyBoard.left:
// If the previous one is out of target range then go to the last image.
if ((base.m_CurrentImageNumber - 1) < 0) {
base.f_OpenImage(base.m_ElementsLength, "left");
} else {
base.f_OpenImage(base.m_CurrentImageNumber - 1, "left");
}
return false;
case keyBoard.right:
// If the next one is out of target range then go to the first image.
if ((base.m_CurrentImageNumber + 1) > base.m_ElementsLength) {
base.f_OpenImage(0, "right");
} else {
base.f_OpenImage(base.m_CurrentImageNumber + 1, "right");
}
return false;
case keyBoard.esc:
base.f_QuitImage();
return false;
}
}
return false;
}, false);
}
// Add document click event.
if (base.m_Options.quitOnDocumentClick) {
document.body.addEventListener("click", function (event) {
var target = event.target ? event.target : event.srcElement;
event.preventDefault();
if (target && target.id != "imagelightbox" && base.m_CurrentImageElement && !base.m_InProgress && base.m_InstalledImageBox) {
base.f_QuitImage();
return false;
}
return false;
}, false);
}
},
f_OpenImage: function (p_WhichOne, p_Direction) {
var base = this,
newFragment = document.createDocumentFragment(),
newImageElement = document.createElement("img"),
target = base.m_Elements[p_WhichOne].getAttribute("href");
if (base.m_CurrentImageElement) {
base.f_RemoveImage();
}
if (base.f_isFunction(base.m_Options.imageLoadStart)) {
base.m_Options.imageLoadStart();
}
base.m_InProgress = true;
base.m_InstalledImageBox = false;
base.m_Direction = typeof p_Direction === "undefined" ? 1 : p_Direction == "left" ? -1 : 1;
newImageElement.setAttribute("src", target);
newImageElement.setAttribute("alt", "LightBox");
newImageElement.setAttribute("id", base.m_Options.boxId);
newFragment.appendChild(newImageElement);
base.m_Body.appendChild(newFragment);
base.m_CurrentImageElement = document.getElementById(base.m_Options.boxId);
base.m_CurrentImageElement.style.opacity = "0";
base.m_CurrentImageNumber = p_WhichOne;
if (base.m_Options.quitOnImageClick) {
base.f_ImageClickEvent = function (event) {
event.preventDefault();
base.f_QuitImage();
};
base.m_CurrentImageElement.addEventListener("click", base.f_ImageClickEvent, false);
}
if (base.browser.isHardwareAccelerated) {
DomM.css.set(base.m_CurrentImageElement, base.f_AddTransitionSpeed(base.m_Options.animationSpeed));
}
base.f_SetImage();
DomM.css.set(base.m_CurrentImageElement, base.f_doTranslateX(50 * base.m_Direction + "px"));
setTimeout(function () {
if (base.browser.isHardwareAccelerated) {
setTimeout(function () {
DomM.css.set(base.m_CurrentImageElement, base.f_doTranslateX("0px"));
}, 50);
}
if (base.f_isFunction(base.m_Options.imageLoadEnd)) {
base.m_Options.imageLoadEnd();
}
}, 20);
setTimeout(function () {
base.m_InProgress = false;
base.m_InstalledImageBox = true;
}, base.m_Options.animationSpeed - 200);
},
f_SetImage: function () {
var base = this,
screenHeight = window.innerHeight || docElem.offsetHeight,
screenWidth = window.innerWidth || docElem.offsetWidth,
tmpImage = new Image(),
imageWidth, imageHeight, imageSizeRatio;
if (!base.m_CurrentImageElement) {
return;
}
tmpImage.onload = function () {
imageWidth = this.width;
imageHeight = this.height;
imageSizeRatio = imageWidth / imageHeight;
if (Math.floor(screenWidth/imageSizeRatio) > screenHeight) {
imageWidth = screenHeight * imageSizeRatio * 0.7;
imageHeight = screenHeight * 0.7;
} else {
imageWidth = screenWidth * 0.7;
imageHeight = screenWidth / imageSizeRatio * 0.7;
}
DomM.css.set(base.m_CurrentImageElement, {
"top": ((screenHeight - imageHeight) / 2) + "px",
"left": ((screenWidth - imageWidth) / 2) + "px",
"width": Math.floor(imageWidth) + "px",
"height": Math.floor(imageHeight) + "px",
"opacity": 1
});
};
tmpImage.src = base.m_CurrentImageElement.getAttribute("src");
},
f_RemoveImage: function () {
var base = this;
if (base.m_CurrentImageElement) {
if (base.f_isFunction(base.m_Options.quitOnImageClick)) {
base.m_CurrentImageElement.removeEventListener("click", base.f_ImageClickEvent, false);
}
base.m_CurrentImageElement.parentNode.removeChild(base.m_CurrentImageElement);
base.m_CurrentImageElement = false;
}
return false;
},
f_QuitImage: function () {
var base = this;
if (base.m_CurrentImageElement) {
setTimeout(function () {
DomM.css.set(base.m_CurrentImageElement, {
"opacity": 0,
"transition": ("opacity " + base.m_Options.fadeOutSpeed + "ms ease")
});
setTimeout(function () {
base.f_RemoveImage();
if (base.f_isFunction(base.m_Options.onImageQuit)) {
base.m_Options.onImageQuit();
}
}, base.m_Options.fadeOutSpeed);
}, 20);
}
},
f_IsValidSource: function (p_Src) {
return new RegExp().test(p_Src);
},
f_doTranslateX: function (p_Pixels) {
return {
"-webkit-transform": "translateX(" + p_Pixels + ")",
"-moz-transform": "translateX(" + p_Pixels + ")",
"-o-transform": "translateX(" + p_Pixels + ")",
"-ms-transform": "translateX(" + p_Pixels + ")",
"transform": "translateX(" + p_Pixels + ")"
};
},
f_AddTransitionSpeed: function (p_Speed) {
var base = this;
return {
"-webkit-transition": "transform " + p_Speed + "ms ease, opacity " + base.m_Options.fadeInSpeed + "ms ease",
"-moz-transition": "transform " + p_Speed + "ms ease, opacity " + base.m_Options.fadeInSpeed + "ms ease",
"-o-transition": "transform " + p_Speed + "ms ease, opacity " + base.m_Options.fadeInSpeed + "ms ease",
"transition": "transform " + p_Speed + "ms ease, opacity " + base.m_Options.fadeInSpeed + "ms ease"
};
}
};
_LightBox.options = {
selector: "[data-imagelightbox]",
boxId: "imagelightbox",
allowedTypes: "png|jpg|jpeg|gif",
quitOnImageClick: true,
quitOnDocumentClick: true,
enableKeyboard: true,
animationSpeed: 750,
fadeInSpeed: 500,
fadeOutSpeed: 200,
imageLoadStart: function () {},
imageLoadEnd: function () {},
onImageQuit: function () {},
onImageStart: function () {}
};
LightBox.init = function (p_Options) {
_LightBox.f_Initialize(p_Options);
};
})(window, document, window.LightBox = window.LightBox || {});
var activityIndicatorOn = function () {
var newE = document.createElement("div"),
newB = document.createElement("div");
newE.setAttribute("id", "imagelightbox-loading");
newE.appendChild(newB);
document.body.appendChild(newE);
},
activityIndicatorOff = function () {
var elE = document.getElementById("imagelightbox-loading");
elE.parentNode.removeChild(elE);
};
LightBox.init({
selector: "[data-simplbox='demo1']",
boxId: "simplbox"
});
LightBox.init({
selector: "[data-simplbox='demo2']",
boxId: "simplbox",
imageLoadStart: activityIndicatorOn,
imageLoadEnd: activityIndicatorOff
});
Your code is almost working. What you handled badly is the fact that you can perform several init. On each init, you overwrite some items, especially with this line :
base.m_Elements = document.querySelectorAll(base.m_Options.selector);
So base.m_Elements will only have the elements of the last init.
( Based on the name 'init' i wonder if the real use case wouldn't be to allow just one call of init... )
Quick-fix is to do one single init with :
LightBox.init({
selector: "[data-simplbox='demo1'],[data-simplbox='demo2']",
boxId: "simplbox"
});
(erase the two calls to init)
And here it works.
http://jsfiddle.net/gamealchemist/66HFU/1/
So i guess either you want to support several init, or (easier to maintain in fact), throw exception on multiple init and expect the lib user to write the right selector in the single init call.
Edit : Super quick fix for your issue : rather than having LightBox as a singleton, have it as a Class :
function LightBox( initArguments ) {
// something like what was done in init
}
LightBox.prototype = {
f_RemoveImage : function() {
} ,
f_OpenImage : function( ..., ... ) {
} ,
...
}
then you can call with no issue :
var demo1LB = new LightBox({ selector: "[data-simplbox='demo1']",
boxId: "simplbox" });
var demo2LB = new LightBox({ selector: "[data-simplbox='demo2']",
boxId: "simplbox" });
Still very new at Javascript and jQuery, so this one is tough for me.
I'm using this Bootstrap Lightbox plugin: https://github.com/duncanmcdougall/Responsive-Lightbox
Trying to use it on this site (WIP): http://lastdetailwd.com/modernmetalfabricators/furniture.html
I'm looking to use the title attribute from the A tags as captions for this lightbox. Any clue on how to get this done?
jQuery:
(function ($) {
'use strict';
$.fn.lightbox = function (options) {
var opts = {
margin: 50,
nav: true,
blur: true,
minSize: 0
};
var plugin = {
items: [],
lightbox: null,
image: null,
current: null,
locked: false,
selector: "#lightbox",
init: function (items) {
plugin.items = items;
plugin.selector = "lightbox-"+Math.random().toString().replace('.','');
if (!plugin.lightbox) {
$('body').append(
'<div id="lightbox" class='+plugin.selector+' style="display:none;">'+
'' +
'<div class="lightbox-nav">'+
'' +
'' +
'</div>' +
'</div>'
);
plugin.lightbox = $("."+plugin.selector);
}
if (plugin.items.length > 1 && opts.nav) {
$('.lightbox-nav', plugin.lightbox).show();
} else {
$('.lightbox-nav', plugin.lightbox).hide();
}
plugin.bindEvents();
},
loadImage: function () {
if(opts.blur) {
$("body").addClass("blurred");
}
$("img", plugin.lightbox).remove();
plugin.lightbox.fadeIn('fast').append('<span class="lightbox-loading"></span>');
var img = $('<img src="' + $(plugin.current).attr('href') + '" draggable="false">');
$(img).load(function () {
$('.lightbox-loading').remove();
plugin.lightbox.append(img);
plugin.image = $("img", plugin.lightbox).hide();
plugin.resizeImage();
});
},
resizeImage: function () {
var ratio, wHeight, wWidth, iHeight, iWidth;
wHeight = $(window).height() - opts.margin;
wWidth = $(window).outerWidth(true) - opts.margin;
plugin.image.width('').height('');
iHeight = plugin.image.height();
iWidth = plugin.image.width();
if (iWidth > wWidth) {
ratio = wWidth / iWidth;
iWidth = wWidth;
iHeight = Math.round(iHeight * ratio);
}
if (iHeight > wHeight) {
ratio = wHeight / iHeight;
iHeight = wHeight;
iWidth = Math.round(iWidth * ratio);
}
plugin.image.width(iWidth).height(iHeight).css({
'top': ($(window).height() - plugin.image.outerHeight()) / 2 + 'px',
'left': ($(window).width() - plugin.image.outerWidth()) / 2 + 'px'
}).show();
plugin.locked = false;
},
getCurrentIndex: function () {
return $.inArray(plugin.current, plugin.items);
},
next: function () {
if (plugin.locked) {
return false;
}
plugin.locked = true;
if (plugin.getCurrentIndex() >= plugin.items.length - 1) {
$(plugin.items[0]).click();
} else {
$(plugin.items[plugin.getCurrentIndex() + 1]).click();
}
},
previous: function () {
if (plugin.locked) {
return false;
}
plugin.locked = true;
if (plugin.getCurrentIndex() <= 0) {
$(plugin.items[plugin.items.length - 1]).click();
} else {
$(plugin.items[plugin.getCurrentIndex() - 1]).click();
}
},
bindEvents: function () {
$(plugin.items).click(function (e) {
if(!$("#lightbox").is(":visible") && ($(window).width() < opts.minSize || $(window).height() < opts.minSize)) {
$(this).attr("target", "_blank");
return;
}
var self = $(this)[0];
e.preventDefault();
plugin.current = self;
plugin.loadImage();
// Bind Keyboard Shortcuts
$(document).on('keydown', function (e) {
// Close lightbox with ESC
if (e.keyCode === 27) {
plugin.close();
}
// Go to next image pressing the right key
if (e.keyCode === 39) {
plugin.next();
}
// Go to previous image pressing the left key
if (e.keyCode === 37) {
plugin.previous();
}
});
});
// Add click state on overlay background only
plugin.lightbox.on('click', function (e) {
if (this === e.target) {
plugin.close();
}
});
// Previous click
$(plugin.lightbox).on('click', '.lightbox-previous', function () {
plugin.previous();
return false;
});
// Next click
$(plugin.lightbox).on('click', '.lightbox-next', function () {
plugin.next();
return false;
});
// Close click
$(plugin.lightbox).on('click', '.lightbox-close', function () {
plugin.close();
return false;
});
$(window).resize(function () {
if (!plugin.image) {
return;
}
plugin.resizeImage();
});
},
close: function () {
$(document).off('keydown'); // Unbind all key events each time the lightbox is closed
$(plugin.lightbox).fadeOut('fast');
$('body').removeClass('blurred');
}
};
$.extend(opts, options);
plugin.init(this);
};
})(jQuery);
Author did just update it to include captions. Closing this.