Working on tap behavior of scatter and flippables.

This commit is contained in:
Uwe Oestermeier 2019-07-05 13:43:39 +02:00
parent 30e998e386
commit 20ac5c387e
7 changed files with 416 additions and 203 deletions

244
dist/iwmlib.js vendored
View File

@ -987,6 +987,13 @@
return Math.sqrt(dx * dx + dy * dy) return Math.sqrt(dx * dx + dy * dy)
} }
// Distance == 0.0 indicates an inside relation.
static distanceToRect(p, r) {
var cx = Math.max(Math.min(p.x, r.x + r.width), r.x);
var cy = Math.max(Math.min(p.y, r.y + r.height), r.y);
return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy))
}
static fromPageToNode(element, p) { static fromPageToNode(element, p) {
// if (window.webkitConvertPointFromPageToNode) { // if (window.webkitConvertPointFromPageToNode) {
// return window.webkitConvertPointFromPageToNode(element, // return window.webkitConvertPointFromPageToNode(element,
@ -3815,38 +3822,6 @@
onMove = null onMove = null
} = {} } = {}
) { ) {
<<<<<<< HEAD
this.onCapture = null;
this.element = element;
if (stopEvents === 'auto') {
/*
The events have to be stopped in Safari, otherwise the whole page will be zoomed with
a pinch gesture (preventDefault in method preventPinch). In order to enable the
movement of scatter objects, the touchmove event has to be bound again.
*/
if (Capabilities.isSafari) {
document.addEventListener(
'touchmove',
event => this.preventPinch(event),
false
);
stopEvents = false;
} else {
stopEvents = true;
}
}
this.stopEvents = stopEvents;
this.claimEvents = claimEvents;
if (touchAction !== null) {
Elements$1.setStyle(element, { touchAction });
}
this.scatter = new Map();
this.delegate = new InteractionMapper$1(element, this, {
useCapture,
mouseWheelElement: window
});
=======
>>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc
let notchPosition = (switchPos && point.y < 50) ? "topCenter" : "bottomCenter"; let notchPosition = (switchPos && point.y < 50) ? "topCenter" : "bottomCenter";
@ -4753,52 +4728,6 @@
} }
} }
<<<<<<< HEAD
class DOMFlippable {
constructor(element, scatter, flip) {
// Set log to console.log or a custom log function
// define data structures to store our touchpoints in
this.element = element;
this.flip = flip;
this.card = element.querySelector('.flipCard');
this.front = element.querySelector('.front');
this.back = element.querySelector('.back');
this.flipped = false;
this.scatter = scatter;
this.onFrontFlipped = flip.onFrontFlipped;
this.onBackFlipped = flip.onBackFlipped;
this.onClose = flip.onClose;
this.onRemoved = flip.onRemoved;
this.onUpdate = flip.onUpdate;
this.flipDuration = flip.flipDuration;
this.fadeDuration = flip.fadeDuration;
scatter.addTransformEventCallback(this.scatterTransformed.bind(this));
console.log('lib.DOMFlippable', 5000);
TweenLite.set(this.element, { perspective: 5000 });
TweenLite.set(this.card, { transformStyle: 'preserve-3d' });
TweenLite.set(this.back, { rotationY: -180 });
TweenLite.set([this.back, this.front], {
backfaceVisibility: 'hidden',
perspective: 5000
});
TweenLite.set(this.front, { visibility: 'visible' });
this.infoBtn = element.querySelector('.infoBtn');
this.backBtn = element.querySelector('.backBtn');
this.closeBtn = element.querySelector('.closeBtn');
/* Buttons are not guaranteed to exist. */
if (this.infoBtn) {
InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start());
this.enable(this.infoBtn);
}
if (this.backBtn) {
InteractionMapper$1.on('tap', this.backBtn, event => this.start());
}
if (this.closeBtn) {
InteractionMapper$1.on('tap', this.closeBtn, event => this.close());
this.enable(this.closeBtn);
=======
/** /**
* For a given zoom, a new scale is calculated, taking * For a given zoom, a new scale is calculated, taking
* min and max scale into account. * min and max scale into account.
@ -4814,7 +4743,6 @@
if (scale < minScale) { if (scale < minScale) {
scale = minScale; scale = minScale;
zoom = scale / this.scale; zoom = scale / this.scale;
>>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc
} }
if (scale > maxScale) { if (scale > maxScale) {
scale = maxScale; scale = maxScale;
@ -5065,6 +4993,11 @@
this.onCapture = null; this.onCapture = null;
this.element = element; this.element = element;
if (stopEvents === 'auto') { if (stopEvents === 'auto') {
/*
The events have to be stopped in Safari, otherwise the whole page will be zoomed with
a pinch gesture (preventDefault in method preventPinch). In order to enable the
movement of scatter objects, the touchmove event has to be bound again.
*/
if (Capabilities.isSafari) { if (Capabilities.isSafari) {
document.addEventListener( document.addEventListener(
'touchmove', 'touchmove',
@ -5118,7 +5051,7 @@
context.stroke(); context.stroke();
} }
requestAnimationFrame(dt => { requestAnimationFrame(dt => {
this.showTouches(dt); this.showTouches(dt, canvas);
}); });
} }
@ -5230,6 +5163,7 @@
height = null, // required height = null, // required
resizable = false, resizable = false,
clickOnTap = false, clickOnTap = false,
allowClickDistance = 44,
verbose = true, verbose = true,
onResize = null, onResize = null,
touchAction = 'none', touchAction = 'none',
@ -5293,7 +5227,8 @@
rotation: this.startRotationDegrees, rotation: this.startRotationDegrees,
transformOrigin: transformOrigin transformOrigin: transformOrigin
}; };
this.tapNodes = new Map();
this.allowClickDistance = allowClickDistance;
// For tweenlite we need initial values in _gsTransform // For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues); TweenLite.set(element, this.initialValues);
@ -5486,16 +5421,99 @@
} }
onTap(event, interaction, point) { onTap(event, interaction, point) {
if (this.clickOnTap) { if (this.clickOnTap) {
let p = Points.fromPageToNode(this.element, point); let directNode = document.elementFromPoint(point.x, point.y);
let element = document.elementFromPoint(p.x, p.y); let nearestNode = this.nearestClickable(event);
if (element != null) {
console.log('tap simulates click'); console.log("onTap", directNode, nearestNode.tagName);
element.click(); if (directNode != null && this.isClickable(directNode)) {
directNode.click();
}
else {
if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) {
let handler = this.tapNodes.get(nearestNode);
console.log("Clicking beneath SVG: to be done", handler);
Events.stop(event);
//nearestNode.click()
}
} }
} }
} }
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler);
this.tapNodes.set(node, handler);
}
else {
InteractionMapper$1.on('tap', node, handler);
}
}
isClickable(node) {
if (node.tagName == 'A')
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable nodes.
* Unfortunately we cannot search for all nodes with an attached 'click' event listener
* See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not
* Therefore we can only detect the following standard cases:
* I. All clickable objects like clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = [];
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node);
}
return result
}
nearestClickable(event) {
let element = this.element;
let clickables = this.clickableNodes();
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y };
let localClick = Points.fromPageToNode(element, globalClick);
let clickRects = clickables.map(link => {
let rect = link.getBoundingClientRect();
let topLeft = Points.fromPageToNode(element, rect);
let center = Points.fromPageToNode(element, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 });
return { x: topLeft.x, y: topLeft.y, width: rect.width, height: rect.height, center, link }
});
let distances = [];
clickRects.forEach(rect => {
let distance = Points.distanceToRect(localClick, rect);
distances.push(parseInt(distance));
});
let closestClickIndex = distances.indexOf(Math.min(...distances));
let closestClickable = clickables[closestClickIndex];
if (distances[closestClickIndex] < this.allowClickDistance) {
console.log("found closest clickables", closestClickable);
return closestClickable
}
return null
}
isDescendant(parent, child) { isDescendant(parent, child) {
let node = child.parentNode; let node = child.parentNode;
while (node != null) { while (node != null) {
@ -5815,6 +5833,7 @@
translatable = true, translatable = true,
scalable = true, scalable = true,
rotatable = true, rotatable = true,
clickOnTap = false,
onFront = null, onFront = null,
onBack = null, onBack = null,
onClose = null, onClose = null,
@ -5834,6 +5853,7 @@
this.translatable = translatable; this.translatable = translatable;
this.scalable = scalable; this.scalable = scalable;
this.rotatable = rotatable; this.rotatable = rotatable;
this.clickOnTap = clickOnTap;
this.onFrontFlipped = onFront; this.onFrontFlipped = onFront;
this.onBackFlipped = onBack; this.onBackFlipped = onBack;
this.onClose = onClose; this.onClose = onClose;
@ -5888,7 +5908,8 @@
translatable: this.translatable, translatable: this.translatable,
scalable: this.scalable, scalable: this.scalable,
rotatable: this.rotatable, rotatable: this.rotatable,
overdoScaling: this.overdoScaling overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
} }
); );
@ -5897,7 +5918,6 @@
} }
if (this.closeOnMinScale) { if (this.closeOnMinScale) {
const removeOnMinScale = function () { const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) { if (scatter.scale <= scatter.minScale) {
this.flippable.close(); this.flippable.close();
@ -5912,11 +5932,7 @@
scatter.onTransform.splice(callbackIdx, 1); scatter.onTransform.splice(callbackIdx, 1);
} }
} }
}.bind(this); }.bind(this);
scatter.addTransformEventCallback(removeOnMinScale); scatter.addTransformEventCallback(removeOnMinScale);
} }
@ -5952,8 +5968,11 @@
} }
start({ targetCenter = null } = {}) { start({ targetCenter = null } = {}) {
console.log('DOMFlip.start', targetCenter); console.log("DOMFlip.start", targetCenter);
if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); if (this.preloadBack) {
this.flippable.start({ duration: this.flipDuration, targetCenter });
}
else { else {
let back = this.cardWrapper.querySelector('.back'); let back = this.cardWrapper.querySelector('.back');
let flippable = this.flippable; let flippable = this.flippable;
@ -6005,7 +6024,7 @@
this.flipDuration = flip.flipDuration; this.flipDuration = flip.flipDuration;
this.fadeDuration = flip.fadeDuration; this.fadeDuration = flip.fadeDuration;
scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); scatter.addTransformEventCallback(this.scatterTransformed.bind(this));
console.log('lib.DOMFlippable', 5000);
TweenLite.set(this.element, { perspective: 5000 }); TweenLite.set(this.element, { perspective: 5000 });
TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); TweenLite.set(this.card, { transformStyle: 'preserve-3d' });
TweenLite.set(this.back, { rotationY: -180 }); TweenLite.set(this.back, { rotationY: -180 });
@ -6018,16 +6037,24 @@
this.backBtn = element.querySelector('.backBtn'); this.backBtn = element.querySelector('.backBtn');
this.closeBtn = element.querySelector('.closeBtn'); this.closeBtn = element.querySelector('.closeBtn');
/* Buttons are not guaranteed to exist. */ /* Buttons are not guaranteed to exist. */
if (this.infoBtn) {
InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start());
if (this.infoBtn) {
scatter.addTapListener(this.infoBtn, event => {
console.log("within click handler", this);
this.flip.start();
});
this.enable(this.infoBtn); this.enable(this.infoBtn);
} }
if (this.backBtn) { if (this.backBtn) {
InteractionMapper$1.on('tap', this.backBtn, event => this.start()); scatter.addTapListener(this.backBtn, event => {
console.log("within click handler", this);
this.start();
});
} }
if (this.closeBtn) { if (this.closeBtn) {
InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); scatter.addTapListener(this.closeBtn, event => {
this.close();
});
this.enable(this.closeBtn); this.enable(this.closeBtn);
} }
this.scaleButtons(); this.scaleButtons();
@ -6077,18 +6104,6 @@
} }
scaleButtons() { scaleButtons() {
//This also works for svgs.
// if (this.infoBtn)
// this.infoBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.backBtn)
// this.backBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.closeBtn)
// this.closeBtn.style.transform = "scale(" + this.buttonScale + ")"
console.log(this.buttonScale);
//// This did not work with svgs!
TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], {
scale: this.buttonScale scale: this.buttonScale
}); });
@ -6101,6 +6116,7 @@
clickInfo() { clickInfo() {
this.bringToFront(); this.bringToFront();
console.log("clickInfo");
this.infoBtn.click(); this.infoBtn.click();
} }
@ -6142,8 +6158,6 @@
} }
} }
enable(button) { enable(button) {
this.show(button, this.fadeDuration); this.show(button, this.fadeDuration);
if (button) { if (button) {
@ -6153,9 +6167,6 @@
disable(button) { disable(button) {
this.hide(button, this.fadeDuration); this.hide(button, this.fadeDuration);
if (button) {
TweenLite.set(button, { pointerEvents: 'none' });
}
} }
start({ targetCenter = null } = {}) { start({ targetCenter = null } = {}) {
@ -6199,7 +6210,6 @@
let x = this.flipped ? xx : this.startX; let x = this.flipped ? xx : this.startX;
let y = this.flipped ? yy : this.startY; let y = this.flipped ? yy : this.startY;
console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved);
let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null;
console.log(this.flipDuration); console.log(this.flipDuration);
TweenLite.to(this.card, this.flipDuration, { TweenLite.to(this.card, this.flipDuration, {

157
dist/iwmlib.pixi.js vendored
View File

@ -3263,6 +3263,13 @@
return Math.sqrt(dx * dx + dy * dy) return Math.sqrt(dx * dx + dy * dy)
} }
// Distance == 0.0 indicates an inside relation.
static distanceToRect(p, r) {
var cx = Math.max(Math.min(p.x, r.x + r.width), r.x);
var cy = Math.max(Math.min(p.y, r.y + r.height), r.y);
return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy))
}
static fromPageToNode(element, p) { static fromPageToNode(element, p) {
// if (window.webkitConvertPointFromPageToNode) { // if (window.webkitConvertPointFromPageToNode) {
// return window.webkitConvertPointFromPageToNode(element, // return window.webkitConvertPointFromPageToNode(element,
@ -6922,6 +6929,7 @@
height = null, // required height = null, // required
resizable = false, resizable = false,
clickOnTap = false, clickOnTap = false,
allowClickDistance = 44,
verbose = true, verbose = true,
onResize = null, onResize = null,
touchAction = 'none', touchAction = 'none',
@ -6985,7 +6993,8 @@
rotation: this.startRotationDegrees, rotation: this.startRotationDegrees,
transformOrigin: transformOrigin transformOrigin: transformOrigin
}; };
this.tapNodes = new Map();
this.allowClickDistance = allowClickDistance;
// For tweenlite we need initial values in _gsTransform // For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues); TweenLite.set(element, this.initialValues);
@ -7178,16 +7187,99 @@
} }
onTap(event, interaction, point) { onTap(event, interaction, point) {
if (this.clickOnTap) { if (this.clickOnTap) {
let p = Points.fromPageToNode(this.element, point); let directNode = document.elementFromPoint(point.x, point.y);
let element = document.elementFromPoint(p.x, p.y); let nearestNode = this.nearestClickable(event);
if (element != null) {
console.log('tap simulates click'); console.log("onTap", directNode, nearestNode.tagName);
element.click(); if (directNode != null && this.isClickable(directNode)) {
directNode.click();
}
else {
if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) {
let handler = this.tapNodes.get(nearestNode);
console.log("Clicking beneath SVG: to be done", handler);
Events$1.stop(event);
//nearestNode.click()
}
} }
} }
} }
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler);
this.tapNodes.set(node, handler);
}
else {
InteractionMapper$1.on('tap', node, handler);
}
}
isClickable(node) {
if (node.tagName == 'A')
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable nodes.
* Unfortunately we cannot search for all nodes with an attached 'click' event listener
* See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not
* Therefore we can only detect the following standard cases:
* I. All clickable objects like clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = [];
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node);
}
return result
}
nearestClickable(event) {
let element = this.element;
let clickables = this.clickableNodes();
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y };
let localClick = Points.fromPageToNode(element, globalClick);
let clickRects = clickables.map(link => {
let rect = link.getBoundingClientRect();
let topLeft = Points.fromPageToNode(element, rect);
let center = Points.fromPageToNode(element, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 });
return { x: topLeft.x, y: topLeft.y, width: rect.width, height: rect.height, center, link }
});
let distances = [];
clickRects.forEach(rect => {
let distance = Points.distanceToRect(localClick, rect);
distances.push(parseInt(distance));
});
let closestClickIndex = distances.indexOf(Math.min(...distances));
let closestClickable = clickables[closestClickIndex];
if (distances[closestClickIndex] < this.allowClickDistance) {
console.log("found closest clickables", closestClickable);
return closestClickable
}
return null
}
isDescendant(parent, child) { isDescendant(parent, child) {
let node = child.parentNode; let node = child.parentNode;
while (node != null) { while (node != null) {
@ -7373,6 +7465,7 @@
translatable = true, translatable = true,
scalable = true, scalable = true,
rotatable = true, rotatable = true,
clickOnTap = false,
onFront = null, onFront = null,
onBack = null, onBack = null,
onClose = null, onClose = null,
@ -7392,6 +7485,7 @@
this.translatable = translatable; this.translatable = translatable;
this.scalable = scalable; this.scalable = scalable;
this.rotatable = rotatable; this.rotatable = rotatable;
this.clickOnTap = clickOnTap;
this.onFrontFlipped = onFront; this.onFrontFlipped = onFront;
this.onBackFlipped = onBack; this.onBackFlipped = onBack;
this.onClose = onClose; this.onClose = onClose;
@ -7446,7 +7540,8 @@
translatable: this.translatable, translatable: this.translatable,
scalable: this.scalable, scalable: this.scalable,
rotatable: this.rotatable, rotatable: this.rotatable,
overdoScaling: this.overdoScaling overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
} }
); );
@ -7455,7 +7550,6 @@
} }
if (this.closeOnMinScale) { if (this.closeOnMinScale) {
const removeOnMinScale = function () { const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) { if (scatter.scale <= scatter.minScale) {
this.flippable.close(); this.flippable.close();
@ -7470,11 +7564,7 @@
scatter.onTransform.splice(callbackIdx, 1); scatter.onTransform.splice(callbackIdx, 1);
} }
} }
}.bind(this); }.bind(this);
scatter.addTransformEventCallback(removeOnMinScale); scatter.addTransformEventCallback(removeOnMinScale);
} }
@ -7510,8 +7600,11 @@
} }
start({ targetCenter = null } = {}) { start({ targetCenter = null } = {}) {
console.log('DOMFlip.start', targetCenter); console.log("DOMFlip.start", targetCenter);
if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); if (this.preloadBack) {
this.flippable.start({ duration: this.flipDuration, targetCenter });
}
else { else {
let back = this.cardWrapper.querySelector('.back'); let back = this.cardWrapper.querySelector('.back');
let flippable = this.flippable; let flippable = this.flippable;
@ -7563,7 +7656,7 @@
this.flipDuration = flip.flipDuration; this.flipDuration = flip.flipDuration;
this.fadeDuration = flip.fadeDuration; this.fadeDuration = flip.fadeDuration;
scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); scatter.addTransformEventCallback(this.scatterTransformed.bind(this));
console.log('lib.DOMFlippable', 5000);
TweenLite.set(this.element, { perspective: 5000 }); TweenLite.set(this.element, { perspective: 5000 });
TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); TweenLite.set(this.card, { transformStyle: 'preserve-3d' });
TweenLite.set(this.back, { rotationY: -180 }); TweenLite.set(this.back, { rotationY: -180 });
@ -7576,15 +7669,24 @@
this.backBtn = element.querySelector('.backBtn'); this.backBtn = element.querySelector('.backBtn');
this.closeBtn = element.querySelector('.closeBtn'); this.closeBtn = element.querySelector('.closeBtn');
/* Buttons are not guaranteed to exist. */ /* Buttons are not guaranteed to exist. */
if (this.infoBtn) { if (this.infoBtn) {
InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); scatter.addTapListener(this.infoBtn, event => {
console.log("within click handler", this);
this.flip.start();
});
this.enable(this.infoBtn); this.enable(this.infoBtn);
} }
if (this.backBtn) { if (this.backBtn) {
InteractionMapper$1.on('tap', this.backBtn, event => this.start()); scatter.addTapListener(this.backBtn, event => {
console.log("within click handler", this);
this.start();
});
} }
if (this.closeBtn) { if (this.closeBtn) {
InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); scatter.addTapListener(this.closeBtn, event => {
this.close();
});
this.enable(this.closeBtn); this.enable(this.closeBtn);
} }
this.scaleButtons(); this.scaleButtons();
@ -7634,18 +7736,6 @@
} }
scaleButtons() { scaleButtons() {
//This also works for svgs.
// if (this.infoBtn)
// this.infoBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.backBtn)
// this.backBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.closeBtn)
// this.closeBtn.style.transform = "scale(" + this.buttonScale + ")"
console.log(this.buttonScale);
//// This did not work with svgs!
TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], {
scale: this.buttonScale scale: this.buttonScale
}); });
@ -7658,6 +7748,7 @@
clickInfo() { clickInfo() {
this.bringToFront(); this.bringToFront();
console.log("clickInfo");
this.infoBtn.click(); this.infoBtn.click();
} }
@ -7699,8 +7790,6 @@
} }
} }
enable(button) { enable(button) {
this.show(button, this.fadeDuration); this.show(button, this.fadeDuration);
if (button) { if (button) {
@ -7710,9 +7799,6 @@
disable(button) { disable(button) {
this.hide(button, this.fadeDuration); this.hide(button, this.fadeDuration);
if (button) {
TweenLite.set(button, { pointerEvents: 'none' });
}
} }
start({ targetCenter = null } = {}) { start({ targetCenter = null } = {}) {
@ -7756,7 +7842,6 @@
let x = this.flipped ? xx : this.startX; let x = this.flipped ? xx : this.startX;
let y = this.flipped ? yy : this.startY; let y = this.flipped ? yy : this.startY;
console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved);
let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null;
console.log(this.flipDuration); console.log(this.flipDuration);
TweenLite.to(this.card, this.flipDuration, { TweenLite.to(this.card, this.flipDuration, {

View File

@ -72,7 +72,7 @@ if (Capabilities.supportsTemplate()) {
flipTemplate, flipTemplate,
new ImageLoader('./examples/king.jpeg'), new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/women.jpeg'), new ImageLoader('./examples/women.jpeg'),
{ onUpdate: e => console.log(e)}) { clickOnTap: true})
flip.load().then((flip) => { flip.load().then((flip) => {
flip.centerAt({ x: 150, y: 120}) flip.centerAt({ x: 150, y: 120})
}) })
@ -80,10 +80,5 @@ if (Capabilities.supportsTemplate()) {
else { else {
alert("Templates not supported, use Edge, Chrome, Safari or Firefox.") alert("Templates not supported, use Edge, Chrome, Safari or Firefox.")
} }
setTimeout(function() {
const infoBtn = document.querySelector('.infoBtn')
InteractionMapper.on('tap', infoBtn, event => console.log('go'))
}, 2000)
</script> </script>
</body> </body>

View File

@ -199,6 +199,7 @@ export class DOMFlip {
translatable = true, translatable = true,
scalable = true, scalable = true,
rotatable = true, rotatable = true,
clickOnTap = false,
onFront = null, onFront = null,
onBack = null, onBack = null,
onClose = null, onClose = null,
@ -218,6 +219,7 @@ export class DOMFlip {
this.translatable = translatable this.translatable = translatable
this.scalable = scalable this.scalable = scalable
this.rotatable = rotatable this.rotatable = rotatable
this.clickOnTap = clickOnTap
this.onFrontFlipped = onFront this.onFrontFlipped = onFront
this.onBackFlipped = onBack this.onBackFlipped = onBack
this.onClose = onClose this.onClose = onClose
@ -272,7 +274,8 @@ export class DOMFlip {
translatable: this.translatable, translatable: this.translatable,
scalable: this.scalable, scalable: this.scalable,
rotatable: this.rotatable, rotatable: this.rotatable,
overdoScaling: this.overdoScaling overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
} }
) )
@ -281,7 +284,6 @@ export class DOMFlip {
} }
if (this.closeOnMinScale) { if (this.closeOnMinScale) {
const removeOnMinScale = function () { const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) { if (scatter.scale <= scatter.minScale) {
this.flippable.close() this.flippable.close()
@ -296,11 +298,7 @@ export class DOMFlip {
scatter.onTransform.splice(callbackIdx, 1) scatter.onTransform.splice(callbackIdx, 1)
} }
} }
}.bind(this) }.bind(this)
scatter.addTransformEventCallback(removeOnMinScale) scatter.addTransformEventCallback(removeOnMinScale)
} }
@ -336,8 +334,11 @@ export class DOMFlip {
} }
start({ targetCenter = null } = {}) { start({ targetCenter = null } = {}) {
console.log('DOMFlip.start', targetCenter) console.log("DOMFlip.start", targetCenter)
if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }) if (this.preloadBack) {
this.flippable.start({ duration: this.flipDuration, targetCenter })
}
else { else {
let back = this.cardWrapper.querySelector('.back') let back = this.cardWrapper.querySelector('.back')
let flippable = this.flippable let flippable = this.flippable
@ -389,7 +390,7 @@ export class DOMFlippable {
this.flipDuration = flip.flipDuration this.flipDuration = flip.flipDuration
this.fadeDuration = flip.fadeDuration this.fadeDuration = flip.fadeDuration
scatter.addTransformEventCallback(this.scatterTransformed.bind(this)) scatter.addTransformEventCallback(this.scatterTransformed.bind(this))
console.log('lib.DOMFlippable', 5000)
TweenLite.set(this.element, { perspective: 5000 }) TweenLite.set(this.element, { perspective: 5000 })
TweenLite.set(this.card, { transformStyle: 'preserve-3d' }) TweenLite.set(this.card, { transformStyle: 'preserve-3d' })
TweenLite.set(this.back, { rotationY: -180 }) TweenLite.set(this.back, { rotationY: -180 })
@ -402,15 +403,24 @@ export class DOMFlippable {
this.backBtn = element.querySelector('.backBtn') this.backBtn = element.querySelector('.backBtn')
this.closeBtn = element.querySelector('.closeBtn') this.closeBtn = element.querySelector('.closeBtn')
/* Buttons are not guaranteed to exist. */ /* Buttons are not guaranteed to exist. */
if (this.infoBtn) { if (this.infoBtn) {
InteractionMapper.on('tap', this.infoBtn, event => this.flip.start()) scatter.addTapListener(this.infoBtn, event => {
console.log("within click handler", this)
this.flip.start()
})
this.enable(this.infoBtn) this.enable(this.infoBtn)
} }
if (this.backBtn) { if (this.backBtn) {
InteractionMapper.on('tap', this.backBtn, event => this.start()) scatter.addTapListener(this.backBtn, event => {
console.log("within click handler", this)
this.start()
})
} }
if (this.closeBtn) { if (this.closeBtn) {
InteractionMapper.on('tap', this.closeBtn, event => this.close()) scatter.addTapListener(this.closeBtn, event => {
this.close()
})
this.enable(this.closeBtn) this.enable(this.closeBtn)
} }
this.scaleButtons() this.scaleButtons()
@ -460,18 +470,6 @@ export class DOMFlippable {
} }
scaleButtons() { scaleButtons() {
//This also works for svgs.
// if (this.infoBtn)
// this.infoBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.backBtn)
// this.backBtn.style.transform = "scale(" + this.buttonScale + ")"
// if (this.closeBtn)
// this.closeBtn.style.transform = "scale(" + this.buttonScale + ")"
console.log(this.buttonScale)
//// This did not work with svgs!
TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], {
scale: this.buttonScale scale: this.buttonScale
}) })
@ -484,6 +482,7 @@ export class DOMFlippable {
clickInfo() { clickInfo() {
this.bringToFront() this.bringToFront()
console.log("clickInfo")
this.infoBtn.click() this.infoBtn.click()
} }
@ -525,8 +524,6 @@ export class DOMFlippable {
} }
} }
enable(button) { enable(button) {
this.show(button, this.fadeDuration) this.show(button, this.fadeDuration)
if (button) { if (button) {
@ -537,7 +534,7 @@ export class DOMFlippable {
disable(button) { disable(button) {
this.hide(button, this.fadeDuration) this.hide(button, this.fadeDuration)
if (button) { if (button) {
TweenLite.set(button, { pointerEvents: 'none' }) // TweenLite.set(button, { pointerEvents: 'none' })
} }
} }
@ -582,7 +579,6 @@ export class DOMFlippable {
let x = this.flipped ? xx : this.startX let x = this.flipped ? xx : this.startX
let y = this.flipped ? yy : this.startY let y = this.flipped ? yy : this.startY
console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved)
let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null
console.log(this.flipDuration) console.log(this.flipDuration)
TweenLite.to(this.card, this.flipDuration, { TweenLite.to(this.card, this.flipDuration, {

View File

@ -15,10 +15,10 @@
context.clearRect(0, 0, debugCanvas.width, debugCanvas.height) context.clearRect(0, 0, debugCanvas.width, debugCanvas.height)
let stage = scatterContainer.polygon let stage = scatterContainer.polygon
stage.draw(context, { stroke: '#FF0000'}) stage.draw(context, { stroke: '#0000FF'})
for(let scatter of scatterContainer.scatter.values()) { for(let scatter of scatterContainer.scatter.values()) {
let polygon = scatter.polygon let polygon = scatter.polygon
polygon.draw(context, { stroke: '#FF0000'}) polygon.draw(context, { stroke: '#0000FF'})
} }
} }
@ -48,7 +48,7 @@ we describe the more basic DOM scatter.
<img id="women" draggable="false" style="position: absolute;" src="examples/women.jpeg" /> <img id="women" draggable="false" style="position: absolute;" src="examples/women.jpeg" />
<img id="king" draggable="false" style="position: absolute;" src="examples/king.jpeg" /> <img id="king" draggable="false" style="position: absolute;" src="examples/king.jpeg" />
<canvas id="debugCanvas" height="280" style="z-index: 100000; pointer-events: none; position: absolute; border: 1px solid red;"> <canvas id="debugCanvas" height="280" style="z-index: 100000; pointer-events: none; position: absolute; border: 1px solid blue;">
Canvas not supported. Canvas not supported.
</canvas> </canvas>
</div> </div>
@ -81,4 +81,39 @@ app.run()
animatePolygons() animatePolygons()
</script> </script>
<h1>
Interactive Content
</h1>
<p>
Scatter objects may contain interactive HTML structures. There is one major flag that allows
to simulate click events by using taps. If the scatter detects a tap it looks for clickable
elements under or nearby the event position and calls the click handler. Thus gestures
can be disambiguated as moves, zooms. or taps.
</p>
<div id="main2" class="grayBorder interactive" style="position: relative; width: 100%; height: 280px;">
<!-- Note that we need to set draggable to false to avoid conflicts. The DOM elements
must also be positioned absolutely. -->
<div id="interactiveContent">
<img draggable="false" style="position: absolute;" src="examples/women.jpeg" />
<a style="position:absolute; top: 10px; right: 10px; color:white;" href="https://www.iwm-tuebingen.de" target="_blank">A Link</a>
<div onclick="alert('clicked')" style="position:absolute; top: 30px; right: 10px; color:white;">A Div with click handler</div>
</div>
</div>
<script class="doctest">
let app2 = new App()
let scatterContainer2 = new DOMScatterContainer(main2)
let scatter2 = new DOMScatter(interactiveContent, scatterContainer2, {
x: 44,
y: 44,
width: 274,
height: 184,
throwVisibility: 88,
minScale: 0.5,
maxScale: 1.5})
app2.run()
</script>
</body> </body>

View File

@ -931,7 +931,7 @@ export class DOMScatterContainer {
context.stroke() context.stroke()
} }
requestAnimationFrame(dt => { requestAnimationFrame(dt => {
this.showTouches(dt) this.showTouches(dt, canvas)
}) })
} }
@ -1043,6 +1043,7 @@ export class DOMScatter extends AbstractScatter {
height = null, // required height = null, // required
resizable = false, resizable = false,
clickOnTap = false, clickOnTap = false,
allowClickDistance = 44,
verbose = true, verbose = true,
onResize = null, onResize = null,
touchAction = 'none', touchAction = 'none',
@ -1106,7 +1107,8 @@ export class DOMScatter extends AbstractScatter {
rotation: this.startRotationDegrees, rotation: this.startRotationDegrees,
transformOrigin: transformOrigin transformOrigin: transformOrigin
} }
this.tapNodes = new Map()
this.allowClickDistance = allowClickDistance
// For tweenlite we need initial values in _gsTransform // For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues) TweenLite.set(element, this.initialValues)
@ -1299,16 +1301,99 @@ export class DOMScatter extends AbstractScatter {
} }
onTap(event, interaction, point) { onTap(event, interaction, point) {
if (this.clickOnTap) { if (this.clickOnTap) {
let p = Points.fromPageToNode(this.element, point) let directNode = document.elementFromPoint(point.x, point.y)
let element = document.elementFromPoint(p.x, p.y) let nearestNode = this.nearestClickable(event)
if (element != null) {
console.log('tap simulates click') console.log("onTap", directNode, nearestNode.tagName)
element.click() if (directNode != null && this.isClickable(directNode)) {
directNode.click()
}
else {
if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) {
let handler = this.tapNodes.get(nearestNode)
console.log("Clicking beneath SVG: to be done", handler)
Events.stop(event)
//nearestNode.click()
}
} }
} }
} }
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler)
this.tapNodes.set(node, handler)
}
else {
InteractionMapper.on('tap', node, handler)
}
}
isClickable(node) {
if (node.tagName == 'A')
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable nodes.
* Unfortunately we cannot search for all nodes with an attached 'click' event listener
* See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not
* Therefore we can only detect the following standard cases:
* I. All clickable objects like clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = []
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node)
}
return result
}
nearestClickable(event) {
let element = this.element
let clickables = this.clickableNodes()
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y }
let localClick = Points.fromPageToNode(element, globalClick)
let clickRects = clickables.map(link => {
let rect = link.getBoundingClientRect()
let topLeft = Points.fromPageToNode(element, rect)
let center = Points.fromPageToNode(element, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 })
return { x: topLeft.x, y: topLeft.y, width: rect.width, height: rect.height, center, link }
})
let distances = []
clickRects.forEach(rect => {
let distance = Points.distanceToRect(localClick, rect)
distances.push(parseInt(distance))
})
let closestClickIndex = distances.indexOf(Math.min(...distances))
let closestClickable = clickables[closestClickIndex]
if (distances[closestClickIndex] < this.allowClickDistance) {
console.log("found closest clickables", closestClickable)
return closestClickable
}
return null
}
isDescendant(parent, child) { isDescendant(parent, child) {
let node = child.parentNode let node = child.parentNode
while (node != null) { while (node != null) {

View File

@ -402,6 +402,13 @@ export class Points {
return Math.sqrt(dx * dx + dy * dy) return Math.sqrt(dx * dx + dy * dy)
} }
// Distance == 0.0 indicates an inside relation.
static distanceToRect(p, r) {
var cx = Math.max(Math.min(p.x, r.x + r.width), r.x)
var cy = Math.max(Math.min(p.y, r.y + r.height), r.y)
return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy))
}
static fromPageToNode(element, p) { static fromPageToNode(element, p) {
// if (window.webkitConvertPointFromPageToNode) { // if (window.webkitConvertPointFromPageToNode) {
// return window.webkitConvertPointFromPageToNode(element, // return window.webkitConvertPointFromPageToNode(element,