Removed setup from constructor to simplify specializations

This commit is contained in:
Uwe Oestermeier 2019-07-08 09:07:26 +02:00
commit 808106262e
19 changed files with 3087 additions and 2694 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist/*
doc/out/*

35
.eslintrc.json Normal file
View File

@ -0,0 +1,35 @@
{
"parserOptions": {
"ecmaVersion": 10,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
}
},
"plugins": [
"mocha"
],
"env": {
"browser": true,
"es6": true,
"node": true,
"jquery": true
},
"globals": {
"PIXI": false,
"TweenLite": false,
"TweenMax": false,
"TimelineLite": false,
"TimelineMax": false,
"SystemJS": false
},
"extends": "eslint:recommended",
"rules": {
"semi": ["error", "never"],
"quotes": ["warn", "single", {"allowTemplateLiterals": true}],
"no-console": "warn",
"no-unused-vars": ["warn", {"argsIgnorePattern": "^(e|event|points|ended)$"}],
"indent": ["warn", 4, {"SwitchCase": 1}],
"mocha/no-exclusive-tests": "error"
}
}

1
.gitignore vendored
View File

@ -79,3 +79,4 @@ typings/
# own
*.code-workspace
.history/
.vscode/

3
.stylelintignore Normal file
View File

@ -0,0 +1,3 @@
dist/*
doc/*
lib/*

11
.stylelintrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"selector-list-comma-newline-after": "never-multi-line",
"no-eol-whitespace": [true, {
"ignore": ["empty-lines"]
}]
},
"ignoreFiles": []
}

View File

@ -1,12 +1,11 @@
html
{
padding: 0px;
html {
padding: 0;
font-size: 16px;
background: white;
font-family: Arial,sans-serif;
font-family: Arial, sans-serif;
color: #000;
max-width: 932px;
margin:0 auto;
margin: 0 auto;
}
.grayBorder {
@ -44,7 +43,6 @@ canvas {
margin-left: 8px;
}
.intrinsic-container {
position: relative;
height: 0;
@ -64,7 +62,7 @@ canvas {
.intrinsic-container iframe {
position: absolute;
border: 0;
top:0;
top: 0;
left: 0;
width: 100%;
height: 100%;

View File

@ -1,6 +1,5 @@
.flipWrapper
{
.flipWrapper {
position: absolute;
top: 0;
left: 0;
@ -17,6 +16,7 @@
height: 100%;
/*** See: https://stackoverflow.com/questions/7439042/css-js-to-prevent-dragging-of-ghost-image ***/
/* -webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
@ -24,21 +24,21 @@
user-drag: none; */
}
.flipFace{
.flipFace {
box-shadow: 2px 2px 10px #000;
visibility: hidden;
}
.front{
.front {
width: 100%;
height: 100%;
position:absolute;
background-color:#333;
position: absolute;
background-color: #333;
}
.back{
background-color:#333;
position:absolute;
.back {
background-color: #333;
position: absolute;
border: 8px solid white;
}
@ -48,8 +48,8 @@
width: 44px;
height: 44px;
padding: 4px;
right: 0px;
top: 0px;
right: 0;
top: 0;
}
.infoBtn {
@ -58,8 +58,8 @@
width: 44px;
height: 44px;
padding: 4px;
right: 0px;
bottom: 0px;
right: 0;
bottom: 0;
}
.backBtn {
@ -68,6 +68,6 @@
width: 44px;
height: 44px;
padding: 4px;
right: 0px;
bottom: 0px;
right: 0;
bottom: 0;
}

View File

@ -1,19 +1,18 @@
html {
height: 100%;
width: 100%;
margin: 0px;
margin: 0;
}
body
{
margin: 0px;
padding: 0px;
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: sans-serif;
font-size: 22pt;
-webkit-tap-highlight-color: #ccc;
background-color: #DDD;
background-color: #ddd;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
@ -22,24 +21,22 @@ body
user-select: none;
-webkit-hyphens: auto;
hyphens: auto;
/* https://davidwalsh.name/font-smoothing */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h3
{
h3 {
color: white;
padding: 4px;
margin: 2px;
background-color: rgba(0, 0, 15, .5);
background-color: rgba(0, 0, 15, 0.5);
}
a { text-decoration: none; }
div.wrapper
{
div.wrapper {
overflow: hidden;
width: 100%;
height: 100%;
@ -52,26 +49,29 @@ div.wrapper
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
}
@-moz-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
}
@-ms-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
}
@-o-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
}
@keyframes color_change {
from { background-color:rgba(0, 0, 0, 0); }
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
}
/*** CSS taken from https://medium.com/@jamesfuthey/simulating-the-creation-of-website-thumbnail-screenshots-using-iframes-7145269891db#.7v7fshos5 ***/
.thumbnail
{
.thumbnail {
position: relative;
-ms-zoom: 0.25;
-moz-transform: scale(0.25);
@ -82,9 +82,7 @@ div.wrapper
-webkit-transform-origin: 0 0;
}
.thumbnail:after
{
.thumbnail::after {
content: "";
display: block;
position: absolute;
@ -95,8 +93,11 @@ div.wrapper
jamesfuthey blog. Otherwise touches would go through on iPad. ***/
}
.thumbnail iframe
{
iframe {
pointer-events: none;
}
.thumbnail iframe {
width: 1024px;
height: 624px;
-webkit-animation-delay: 3s; /* Safari 4.0 - 8.0 */
@ -108,22 +109,20 @@ div.wrapper
animation: color_change 1s infinite alternate;
}
.thumbnail-container
{
.thumbnail-container {
width: calc(1024px * 0.25);
height: calc(624px * 0.25);
display: inline-block;
overflow: hidden;
position: relative;
box-shadow: 2px 2px 10px #000;
color: #DDD;
color: #ddd;
}
div.preview
{
div.preview {
display: inline-block;
margin: 22px;
padding: 0px;
padding: 0;
color: #333;
font-size: 12pt;
text-align: center;
@ -131,8 +130,7 @@ div.preview
height: 196px;
}
div.title
{
div.title {
padding-top: 8px;
width: 256px;
height: 20px;
@ -140,10 +138,9 @@ div.title
overflow: hidden;
}
.container
{
margin: 0px;
padding: 0px;
.container {
margin: 0;
padding: 0;
border: 2pt #000;
min-height: 100%;
min-width: 100%;
@ -156,12 +153,7 @@ div.title
align-content: flex-end;
}
iframe {
pointer-events: none;
}
/** See https://github.com/electron/electron/issues/4420 */
::selection {
background: transparent;
}

4499
dist/iwmlib.js vendored

File diff suppressed because it is too large Load Diff

338
dist/iwmlib.pixi.js vendored
View File

@ -3263,6 +3263,13 @@
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) {
// if (window.webkitConvertPointFromPageToNode) {
// return window.webkitConvertPointFromPageToNode(element,
@ -4781,7 +4788,7 @@
}
}
/* globals Hammer, propagating */
/* eslint-disable no-unused-vars */
/** Interaction patterns
@ -4789,6 +4796,7 @@
*/
class IInteractionTarget extends Interface {
capture(event) {
return typeof true
}
@ -5199,7 +5207,6 @@
}
let result = false;
if (this.isTap(key)) {
this.registerTap(key, ended);
result = this.tapCounts.get(key) == 2;
}
@ -5313,7 +5320,9 @@
if (this.capturePointerEvents) {
try {
element.setPointerCapture(e.pointerId);
} catch (e) { }
} catch (e) {
console.warn('Cannot setPointerCapture');
}
}
this.onStart(e);
}
@ -5345,7 +5354,9 @@
if (this.capturePointerEvents) {
try {
element.releasePointerCapture(e.pointerId);
} catch (e) { }
} catch (e) {
console.warn('Cannot release pointer');
}
}
},
useCapture
@ -5492,9 +5503,8 @@
e => {
if (e.target == element) {
this.onEnd(e);
console.warn("Shouldn't happen: mouseout ends interaction");
console.warn('Shouldn\'t happen: mouseout ends interaction');
}
},
useCapture
);
@ -5594,20 +5604,23 @@
// 'targetTouches'
let result = {};
switch (event.constructor.name) {
case 'MouseEvent':
case 'MouseEvent': {
let buttons = event.buttons || event.which;
if (buttons) result['mouse'] = this.getPosition(event);
break
case 'PointerEvent':
}
case 'PointerEvent': {
result[event.pointerId.toString()] = this.getPosition(event);
break
case 'Touch':
}
case 'Touch': {
let id =
event.touchType === 'stylus'
? 'stylus'
: event.identifier.toString();
result[id] = this.getPosition(event);
break
}
// case 'TouchEvent':
// // Needs to be observed: Perhaps changedTouches are all we need. If so
// // we can remove the touchEventKey default parameter
@ -5654,7 +5667,7 @@
let point = extracted[key];
let updated = this.interaction.update(key, point);
if (updated) {
console.warn("new pointer in updateInteraction shouldn't happen", key);
console.warn('new pointer in updateInteraction shouldn\'t happen', key);
this.interactionStarted(event, key, point);
}
}
@ -5805,6 +5818,7 @@
* @param {object} [opts] - An options object. See the hammer documentation for more details.
*/
static on(types, elements, cb, opts = {}) {
opts = Object.assign({}, {
}, opts);
@ -6039,6 +6053,11 @@
window.Capabilities = Capabilities;
window.CapabilitiesTests = CapabilitiesTests;
/** Basic class for poppable elements that need to be closed as soon as one poppable is
* shown.
*/
/* eslint-disable no-unused-vars */
/**
* A base class for scatter specific events.
*
@ -6088,7 +6107,7 @@
toString() {
return (
"Event('scatterTransformed', scale: " +
'Event(\'scatterTransformed\', scale: ' +
this.scale +
' about: ' +
this.about.x +
@ -6163,7 +6182,7 @@
// Avoid division by zero errors later on
// and consider the number of involved pointers sind addVelocity will be called by the
// onMove events
let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number};
let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number };
this.velocities.push(velocity);
while (this.velocities.length > buffer) {
this.velocities.shift();
@ -6302,7 +6321,8 @@
onThrowFinished = null,
scaleAutoClose = false,
scaleCloseThreshold = 0.10,
scaleCloseBuffer = 0.05
scaleCloseBuffer = 0.05,
maxRotation = Angle.degree2radian(5)
} = {}) {
if (rotationDegrees != null && rotation != null) {
throw new Error('Use rotationDegrees or rotation but not both')
@ -6334,6 +6354,7 @@
this.startScale = startScale; // Needed to reset object
this.minScale = minScale;
this.maxScale = maxScale;
this.maxRotation = maxRotation;
this.overdoScaling = overdoScaling;
this.translatable = translatable;
if (!translatable) {
@ -6345,6 +6366,7 @@
this.resizable = resizable;
this.mouseZoomFactor = mouseZoomFactor;
this.autoBringToFront = autoBringToFront;
this.dragging = false;
this.onTransform = onTransform != null ? [onTransform] : null;
this.onClose = onClose != null ? [onClose] : null;
@ -6379,10 +6401,15 @@
gesture(interaction) {
let delta = interaction.delta();
//console.log("gesture", delta)
if (delta != null) {
this.addVelocity(delta);
this.transform(delta, delta.zoom, delta.rotate, delta.about);
let alpha = delta.rotate;
if (this.maxRotation != null) {
if (Math.abs(alpha) > this.maxRotation) {
alpha = 0;
}
}
this.transform(delta, delta.zoom, alpha, delta.about);
if (delta.zoom != 1) this.interactionAnchor = delta.about;
}
}
@ -6451,7 +6478,7 @@
let stagePolygon = this.containerPolygon;
// UO: since keepOnStage is called in nextVelocity we need to
// ensure a return value
if (!stagePolygon) return { x: 0, y: 0}
if (!stagePolygon) return { x: 0, y: 0 }
let polygon = this.polygon;
let bounced = this.bouncing();
if (bounced) {
@ -6735,20 +6762,6 @@
}
this._updateTransparency();
}
//
// if (this.onTransform != null) {
// let event = new ScatterEvent(this, {
// translate: {x: 0, y: 0},
// scale: this.scale,
// rotate: 0,
// about: null,
// fast: false,
// type: ZOOM
// })
// this.onTransform.forEach(function(f) {
// f(event)
// })
// }
}
onStart(event, interaction) {
@ -6915,7 +6928,9 @@
width = null, // required
height = null, // required
resizable = false,
simulateClick = false,
clickOnTap = false,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true,
onResize = null,
touchAction = 'none',
@ -6966,7 +6981,8 @@
this.height = height;
this.throwVisibility = Math.min(width, height, throwVisibility);
this.container = container;
this.simulateClick = simulateClick;
this.clickOnTap = clickOnTap;
this.triggerSVGClicks = triggerSVGClicks;
this.scale = startScale;
this.rotationDegrees = this.startRotationDegrees;
this.transformOrigin = transformOrigin;
@ -6979,7 +6995,8 @@
rotation: this.startRotationDegrees,
transformOrigin: transformOrigin
};
this.tapNodes = new Map();
this.allowClickDistance = allowClickDistance;
// For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues);
@ -6990,15 +7007,13 @@
}
this.resizeButton = null;
if (resizable) {
let button = document.createElement("div");
button.style.position = "absolute";
button.style.right = "0px";
button.style.bottom = "0px";
button.style.width = "50px";
button.style.height = "50px";
// button.style.borderRadius = "100% 0px 0px 0px";
// button.style.background = this.element.style.backgroundColor
button.className = "interactiveElement";
let button = document.createElement('div');
button.style.position = 'absolute';
button.style.right = '0px';
button.style.bottom = '0px';
button.style.width = '50px';
button.style.height = '50px';
button.className = 'interactiveElement';
this.element.appendChild(button);
button.addEventListener('pointerdown', (e) => {
@ -7014,6 +7029,25 @@
});
this.resizeButton = button;
}
if (clickOnTap) {
/* Since the tap triggers a synthetic click event
we must prevent the original trusted click event which
is also dispatched by the system.
*/
element.addEventListener('click', event => {
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.target.ownerSVGElement) {
if (this.triggerSVGClicks) {
return
}
}
else if (event.isTrusted) {
Events$1.stop(event);
}
}, true);
}
container.add(this);
}
@ -7173,45 +7207,109 @@
TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ });
}
toggleVideo(element) {
if (element.paused) {
element.play();
} else {
element.pause();
onTap(event, interaction, point) {
if (this.clickOnTap) {
let directNode = document.elementFromPoint(event.clientX, event.clientY);
console.log("onTap", event);
if (this.isClickable(directNode)) {
directNode.click();
}
else {
let nearestNode = this.nearestClickable(event);
if (this.isClickable(nearestNode)) {
/* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a
proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page.
Therefore we use the original click event (see constructor).
*/
if (nearestNode.tagName == 'svg') {
let handler = this.tapNodes.get(nearestNode);
console.log("Clicking beneath SVG: to be done", handler);
if (this.triggerSVGClicks)
nearestNode.dispatchEvent(new Event('click'));
return
}
console.log("nearestNode clicked");
nearestNode.click();
}
}
}
}
onTap(event, interaction, point) {
if (this.simulateClick) {
let p = Points.fromPageToNode(this.element, point);
let iframe = this.element.querySelector('iframe');
if (iframe) {
let doc = iframe.contentWindow.document;
let element = doc.elementFromPoint(p.x, p.y);
if (element == null) {
return
/**
* 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);
}
switch (element.tagName) {
case 'VIDEO':
console.log(element.currentSrc);
if (PopupMenu) {
PopupMenu.open(
{
Fullscreen: () =>
window.open(element.currentSrc),
Play: () => element.play()
},
{ x, y }
);
} else {
this.toggleVideo(element);
}
break
default:
element.click();
else {
InteractionMapper$1.on('tap', node, handler);
}
}
isClickable(node) {
if (node == null)
return false
if (node.tagName == 'A')
return true
if (node.hasAttribute("onclick"))
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) {
return closestClickable
}
return null
}
isDescendant(parent, child) {
@ -7250,37 +7348,35 @@
}
resizeAfterTransform(zoom) {
// let w = this.width * this.scale
// let h = this.height * this.scale
// TweenLite.set(this.element, { width: w, height: h })
if (this.onResize) {
let w = this.width * this.scale;
let h = this.height * this.scale;
let event = new ResizeEvent(this, { width: w, height: h });
this.onResize(event);
}
if (this.resizeButton != null) ;
}
startResize(e) {
e.preventDefault();
let event = new CustomEvent('resizeStarted');
let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top };
this.bringToFront();
this.element.style.transformOrigin = "0% 0%";
this.element.style.transformOrigin = '0% 0%';
let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top };
let offset = Points.subtract(oldPostition, newPostition);
this.oldX = e.clientX;
this.oldY = e.clientY;
e.target.setAttribute('resizing', "true");
e.target.setAttribute('resizing', 'true');
e.target.setPointerCapture(e.pointerId);
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } });
TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } });
TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } });
TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } });
this.element.dispatchEvent(event);
}
@ -7291,7 +7387,7 @@
let rotation = Angle.radian2degree(this.rotation);
rotation = (rotation + 360) % 360;
let event = new CustomEvent('resized');
if (e.target.getAttribute('resizing') == "true") {
if (e.target.getAttribute('resizing') == 'true') {
let deltaX = (e.clientX - this.oldX);
let deltaY = (e.clientY - this.oldY);
@ -7322,15 +7418,15 @@
e.preventDefault();
let event = new CustomEvent('resizeEnded');
let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
this.element.style.transformOrigin = "50% 50%";
let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top };
this.element.style.transformOrigin = '50% 50%';
let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top };
let offset = Points.subtract(oldPostition, newPostition);
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } });
TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } });
TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } });
TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } });
e.target.setAttribute('resizing', "false");
e.target.setAttribute('resizing', 'false');
this.element.dispatchEvent(event);
}
@ -7401,6 +7497,7 @@
translatable = true,
scalable = true,
rotatable = true,
clickOnTap = false,
onFront = null,
onBack = null,
onClose = null,
@ -7420,6 +7517,7 @@
this.translatable = translatable;
this.scalable = scalable;
this.rotatable = rotatable;
this.clickOnTap = clickOnTap;
this.onFrontFlipped = onFront;
this.onBackFlipped = onBack;
this.onClose = onClose;
@ -7474,7 +7572,8 @@
translatable: this.translatable,
scalable: this.scalable,
rotatable: this.rotatable,
overdoScaling: this.overdoScaling
overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
}
);
@ -7483,7 +7582,6 @@
}
if (this.closeOnMinScale) {
const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) {
this.flippable.close();
@ -7498,11 +7596,7 @@
scatter.onTransform.splice(callbackIdx, 1);
}
}
}.bind(this);
scatter.addTransformEventCallback(removeOnMinScale);
}
@ -7529,6 +7623,7 @@
}
setupFlippable(flippable, loader) {
console.log("setupFlippable", loader.wantedWidth);
flippable.wantedWidth = loader.wantedWidth;
flippable.wantedHeight = loader.wantedHeight;
flippable.wantedScale = loader.scale;
@ -7538,8 +7633,11 @@
}
start({ targetCenter = null } = {}) {
console.log('DOMFlip.start', targetCenter);
if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter });
console.log("DOMFlip.start", targetCenter);
if (this.preloadBack) {
this.flippable.start({ duration: this.flipDuration, targetCenter });
}
else {
let back = this.cardWrapper.querySelector('.back');
let flippable = this.flippable;
@ -7588,10 +7686,16 @@
this.onRemoved = flip.onRemoved;
this.onUpdate = flip.onUpdate;
this.wantedWidth = scatter.width;
this.wantedHeight = scatter.height;
this.wantedScale = scatter.scale;
this.minScale = scatter.minScale;
this.maxScale = scatter.maxScale;
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 });
@ -7604,16 +7708,22 @@
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());
if (this.infoBtn) {
scatter.addTapListener(this.infoBtn, event => {
this.flip.start();
});
this.enable(this.infoBtn);
}
if (this.backBtn) {
InteractionMapper$1.on('tap', this.backBtn, event => this.start());
scatter.addTapListener(this.backBtn, event => {
this.start();
});
}
if (this.closeBtn) {
InteractionMapper$1.on('tap', this.closeBtn, event => this.close());
scatter.addTapListener(this.closeBtn, event => {
this.close();
});
this.enable(this.closeBtn);
}
this.scaleButtons();
@ -7663,18 +7773,6 @@
}
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], {
scale: this.buttonScale
});
@ -7687,6 +7785,7 @@
clickInfo() {
this.bringToFront();
console.log("clickInfo");
this.infoBtn.click();
}
@ -7728,8 +7827,6 @@
}
}
enable(button) {
this.show(button, this.fadeDuration);
if (button) {
@ -7785,15 +7882,15 @@
let x = this.flipped ? xx : this.startX;
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;
console.log(this.flipDuration);
console.log("start", this.flipDuration);
TweenLite.to(this.card, this.flipDuration, {
rotationY: targetY,
ease: Power1.easeOut,
transformOrigin: '50% 50%',
onUpdate,
onComplete: e => {
console.log("start end", this.flipDuration);
if (this.flipped) {
//this.hide(this.front)
this.enable(this.backBtn);
@ -7826,6 +7923,7 @@
force3D: true
});
console.log("start 2", this.wantedWidth, this.startWidth, {w, h});
// See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/
TweenLite.to(this.element, this.flipDuration / 2, {
scale: targetScale,

View File

@ -66,13 +66,13 @@ templates.
</main>
<script class="doctest">
let scatterContainer = new DOMScatterContainer(main)
let scatterContainer = new DOMScatterContainer(main, {stopEvents: false})
if (Capabilities.supportsTemplate()) {
let flip = new DOMFlip(scatterContainer,
flipTemplate,
new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/women.jpeg'),
{ onUpdate: e => console.log(e)})
{ clickOnTap: true})
flip.load().then((flip) => {
flip.centerAt({ x: 150, y: 120})
})

View File

@ -199,6 +199,7 @@ export class DOMFlip {
translatable = true,
scalable = true,
rotatable = true,
clickOnTap = false,
onFront = null,
onBack = null,
onClose = null,
@ -218,6 +219,7 @@ export class DOMFlip {
this.translatable = translatable
this.scalable = scalable
this.rotatable = rotatable
this.clickOnTap = clickOnTap
this.onFrontFlipped = onFront
this.onBackFlipped = onBack
this.onClose = onClose
@ -272,7 +274,8 @@ export class DOMFlip {
translatable: this.translatable,
scalable: this.scalable,
rotatable: this.rotatable,
overdoScaling: this.overdoScaling
overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
}
)
@ -281,7 +284,6 @@ export class DOMFlip {
}
if (this.closeOnMinScale) {
const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) {
this.flippable.close()
@ -296,11 +298,7 @@ export class DOMFlip {
scatter.onTransform.splice(callbackIdx, 1)
}
}
}.bind(this)
scatter.addTransformEventCallback(removeOnMinScale)
}
@ -327,6 +325,7 @@ export class DOMFlip {
}
setupFlippable(flippable, loader) {
console.log("setupFlippable", loader.wantedWidth)
flippable.wantedWidth = loader.wantedWidth
flippable.wantedHeight = loader.wantedHeight
flippable.wantedScale = loader.scale
@ -336,8 +335,11 @@ export class DOMFlip {
}
start({ targetCenter = null } = {}) {
console.log('DOMFlip.start', targetCenter)
if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter })
console.log("DOMFlip.start", targetCenter)
if (this.preloadBack) {
this.flippable.start({ duration: this.flipDuration, targetCenter })
}
else {
let back = this.cardWrapper.querySelector('.back')
let flippable = this.flippable
@ -386,10 +388,16 @@ export class DOMFlippable {
this.onRemoved = flip.onRemoved
this.onUpdate = flip.onUpdate
this.wantedWidth = scatter.width
this.wantedHeight = scatter.height
this.wantedScale = scatter.scale
this.minScale = scatter.minScale
this.maxScale = scatter.maxScale
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 })
@ -402,16 +410,22 @@ export class DOMFlippable {
this.backBtn = element.querySelector('.backBtn')
this.closeBtn = element.querySelector('.closeBtn')
/* Buttons are not guaranteed to exist. */
if (this.infoBtn) {
InteractionMapper.on('tap', this.infoBtn, event => this.flip.start())
if (this.infoBtn) {
scatter.addTapListener(this.infoBtn, event => {
this.flip.start()
})
this.enable(this.infoBtn)
}
if (this.backBtn) {
InteractionMapper.on('tap', this.backBtn, event => this.start())
scatter.addTapListener(this.backBtn, event => {
this.start()
})
}
if (this.closeBtn) {
InteractionMapper.on('tap', this.closeBtn, event => this.close())
scatter.addTapListener(this.closeBtn, event => {
this.close()
})
this.enable(this.closeBtn)
}
this.scaleButtons()
@ -461,18 +475,6 @@ export class DOMFlippable {
}
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], {
scale: this.buttonScale
})
@ -485,6 +487,7 @@ export class DOMFlippable {
clickInfo() {
this.bringToFront()
console.log("clickInfo")
this.infoBtn.click()
}
@ -526,8 +529,6 @@ export class DOMFlippable {
}
}
enable(button) {
this.show(button, this.fadeDuration)
if (button) {
@ -583,15 +584,15 @@ export class DOMFlippable {
let x = this.flipped ? xx : this.startX
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
console.log(this.flipDuration)
console.log("start", this.flipDuration)
TweenLite.to(this.card, this.flipDuration, {
rotationY: targetY,
ease: Power1.easeOut,
transformOrigin: '50% 50%',
onUpdate,
onComplete: e => {
console.log("start end", this.flipDuration)
if (this.flipped) {
//this.hide(this.front)
this.enable(this.backBtn)
@ -624,6 +625,7 @@ export class DOMFlippable {
force3D: true
})
console.log("start 2", this.wantedWidth, this.startWidth, {w, h})
// See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/
TweenLite.to(this.element, this.flipDuration / 2, {
scale: targetScale,

View File

@ -1,8 +1,9 @@
/* eslint-disable no-unused-vars */
/* globals Hammer, propagating */
/*eslint no-console: ["error", { allow: ["log", "warn", "info", "error"] }]*/
import Interface from './interface.js'
import { Points, Angle, MapProxy } from './utils.js'
import { Points, MapProxy } from './utils.js'
import Events from './events.js'
import Logging from './logging.js'
@ -12,6 +13,7 @@ import Logging from './logging.js'
*/
export class IInteractionTarget extends Interface {
capture(event) {
return typeof true
}
@ -422,7 +424,6 @@ export class Interaction extends InteractionPoints {
}
let result = false
if (this.isTap(key)) {
this.registerTap(key, ended)
result = this.tapCounts.get(key) == 2
}
@ -536,7 +537,9 @@ export class InteractionDelegate {
if (this.capturePointerEvents) {
try {
element.setPointerCapture(e.pointerId)
} catch (e) { }
} catch (e) {
console.warn('Cannot setPointerCapture')
}
}
this.onStart(e)
}
@ -568,7 +571,9 @@ export class InteractionDelegate {
if (this.capturePointerEvents) {
try {
element.releasePointerCapture(e.pointerId)
} catch (e) { }
} catch (e) {
console.warn('Cannot release pointer')
}
}
},
useCapture
@ -715,9 +720,8 @@ export class InteractionDelegate {
e => {
if (e.target == element) {
this.onEnd(e)
console.warn("Shouldn't happen: mouseout ends interaction")
console.warn('Shouldn\'t happen: mouseout ends interaction')
}
},
useCapture
)
@ -819,20 +823,23 @@ export class InteractionDelegate {
// 'targetTouches'
let result = {}
switch (event.constructor.name) {
case 'MouseEvent':
case 'MouseEvent': {
let buttons = event.buttons || event.which
if (buttons) result['mouse'] = this.getPosition(event)
break
case 'PointerEvent':
}
case 'PointerEvent': {
result[event.pointerId.toString()] = this.getPosition(event)
break
case 'Touch':
}
case 'Touch': {
let id =
event.touchType === 'stylus'
? 'stylus'
: event.identifier.toString()
result[id] = this.getPosition(event)
break
}
// case 'TouchEvent':
// // Needs to be observed: Perhaps changedTouches are all we need. If so
// // we can remove the touchEventKey default parameter
@ -879,7 +886,7 @@ export class InteractionDelegate {
let point = extracted[key]
let updated = this.interaction.update(key, point)
if (updated) {
console.warn("new pointer in updateInteraction shouldn't happen", key)
console.warn('new pointer in updateInteraction shouldn\'t happen', key)
this.interactionStarted(event, key, point)
}
}
@ -1032,6 +1039,7 @@ export class InteractionMapper extends InteractionDelegate {
* @param {object} [opts] - An options object. See the hammer documentation for more details.
*/
static on(types, elements, cb, opts = {}) {
opts = Object.assign({}, {
}, opts)

View File

@ -13,12 +13,11 @@
debugCanvas.width = main.getBoundingClientRect().width
let context = debugCanvas.getContext('2d')
context.clearRect(0, 0, debugCanvas.width, debugCanvas.height)
let stage = scatterContainer.polygon
stage.draw(context, { stroke: '#FF0000'})
stage.draw(context, { stroke: "#0000FF"})
for(let scatter of scatterContainer.scatter.values()) {
let polygon = scatter.polygon
polygon.draw(context, { stroke: '#FF0000'})
polygon.draw(context, { stroke: '#0000FF'})
}
}
@ -26,6 +25,7 @@
requestAnimationFrame((dt) => {
drawPolygons()
animatePolygons()
})
}
</script>
@ -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="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>
</div>
@ -80,5 +80,47 @@ for(let key of ['women', 'king']) {
app.run()
animatePolygons()
</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.
Note that on touch devices you can tap beside the object if you use clickOnTap. The allowed distance
can be configured by allowClickDistance. The default value is 44px.
</p>
<div id="contentExample" class="grayBorder interactive" style="position: relative; width: 100%; height: 280px;">
<div id="interactiveContent">
<img draggable="false" style="position: absolute;" src="examples/women.jpeg" />
<a style="position:absolute; top: 10px; right: 10px; color:white;" href="javascript:alert('test link')">A Link</a>
<div onclick="alert('div clicked')" style="position:absolute; top: 30px; right: 10px; color:white;">A Div with click handler</div>
<svg onclick="alert('svg clicked')" style="position: absolute; right: 0px; bottom: 0px; width: 32px; height: 32px;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<circle cx="50" cy="50" r="44" stroke="white" stroke-width="8" fill="gray" />
<circle cx="50" cy="32" r="7" fill="white" />
<line x1="50" y1="46" x2="50" y2="78" stroke="white" stroke-width="12" />
</svg>
</div>
</div>
<script class="doctest">
let contentContainer = new DOMScatterContainer(contentExample)
new DOMScatter(interactiveContent, contentContainer, {
x: 44,
y: 44,
width: 274,
height: 184,
clickOnTap: true,
throwVisibility: 88,
triggerSVGClicks: true,
minScale: 0.5,
maxScale: 1.5})
</script>
</body>

View File

@ -1,8 +1,11 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-console */
/* globals TweenLite debugCanvas */
import { Points, Polygon, Angle, Elements } from './utils.js'
import Events from './events.js'
import { InteractionMapper } from './interaction.js'
import { Capabilities } from './capabilities.js'
import PopupMenu from './popupmenu.js'
/**
* A base class for scatter specific events.
*
@ -21,8 +24,6 @@ export class BaseEvent {
const START = 'onStart'
const UPDATE = 'onUpdate'
const END = 'onEnd'
const ZOOM = 'onZoom'
const MOVE = 'onMove'
/**
* A scatter event that describes how the scatter has changed.
@ -54,7 +55,7 @@ export class ScatterEvent extends BaseEvent {
toString() {
return (
"Event('scatterTransformed', scale: " +
'Event(\'scatterTransformed\', scale: ' +
this.scale +
' about: ' +
this.about.x +
@ -129,7 +130,7 @@ class Throwable {
// Avoid division by zero errors later on
// and consider the number of involved pointers sind addVelocity will be called by the
// onMove events
let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number}
let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number }
this.velocities.push(velocity)
while (this.velocities.length > buffer) {
this.velocities.shift()
@ -268,7 +269,8 @@ export class AbstractScatter extends Throwable {
onThrowFinished = null,
scaleAutoClose = false,
scaleCloseThreshold = 0.10,
scaleCloseBuffer = 0.05
scaleCloseBuffer = 0.05,
maxRotation = Angle.degree2radian(5)
} = {}) {
if (rotationDegrees != null && rotation != null) {
throw new Error('Use rotationDegrees or rotation but not both')
@ -300,6 +302,7 @@ export class AbstractScatter extends Throwable {
this.startScale = startScale // Needed to reset object
this.minScale = minScale
this.maxScale = maxScale
this.maxRotation = maxRotation
this.overdoScaling = overdoScaling
this.translatable = translatable
if (!translatable) {
@ -311,6 +314,7 @@ export class AbstractScatter extends Throwable {
this.resizable = resizable
this.mouseZoomFactor = mouseZoomFactor
this.autoBringToFront = autoBringToFront
this.dragging = false
this.onTransform = onTransform != null ? [onTransform] : null
this.onClose = onClose != null ? [onClose] : null
@ -345,10 +349,15 @@ export class AbstractScatter extends Throwable {
gesture(interaction) {
let delta = interaction.delta()
//console.log("gesture", delta)
if (delta != null) {
this.addVelocity(delta)
this.transform(delta, delta.zoom, delta.rotate, delta.about)
let alpha = delta.rotate
if (this.maxRotation != null) {
if (Math.abs(alpha) > this.maxRotation) {
alpha = 0
}
}
this.transform(delta, delta.zoom, alpha, delta.about)
if (delta.zoom != 1) this.interactionAnchor = delta.about
}
}
@ -417,7 +426,7 @@ export class AbstractScatter extends Throwable {
let stagePolygon = this.containerPolygon
// UO: since keepOnStage is called in nextVelocity we need to
// ensure a return value
if (!stagePolygon) return { x: 0, y: 0}
if (!stagePolygon) return { x: 0, y: 0 }
let polygon = this.polygon
let bounced = this.bouncing()
if (bounced) {
@ -701,20 +710,6 @@ export class AbstractScatter extends Throwable {
}
this._updateTransparency()
}
//
// if (this.onTransform != null) {
// let event = new ScatterEvent(this, {
// translate: {x: 0, y: 0},
// scale: this.scale,
// rotate: 0,
// about: null,
// fast: false,
// type: ZOOM
// })
// this.onTransform.forEach(function(f) {
// f(event)
// })
// }
}
onStart(event, interaction) {
@ -829,10 +824,10 @@ export class AbstractScatter extends Throwable {
scale: this.scale,
fast: false,
type: null
});
})
this.onTransform.forEach(function (f) {
f(event);
});
f(event)
})
}
}
@ -870,14 +865,20 @@ export class DOMScatterContainer {
* @param {String} [touchAction=none] - CSS to set touch action style, needed to prevent
* pointer cancel events. Use null if the
* the touch action should not be set.
* @param {DOM node} debugCanvas - Shows debug infos about touches if not null
*/
constructor(
element,
{ stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none' } = {}
{ stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none', debugCanvas = null } = {}
) {
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',
@ -900,16 +901,15 @@ export class DOMScatterContainer {
mouseWheelElement: window
})
if (typeof debugCanvas !== 'undefined') {
if (debugCanvas !== null) {
requestAnimationFrame(dt => {
this.showTouches(dt)
this.showTouches(dt, debugCanvas)
})
}
}
showTouches(dt) {
showTouches(dt, canvas) {
let resolution = window.devicePixelRatio
let canvas = debugCanvas
let current = this.delegate.interaction.current
let context = canvas.getContext('2d')
let radius = 20 * resolution
@ -932,7 +932,7 @@ export class DOMScatterContainer {
context.stroke()
}
requestAnimationFrame(dt => {
this.showTouches(dt)
this.showTouches(dt, canvas)
})
}
@ -1043,7 +1043,9 @@ export class DOMScatter extends AbstractScatter {
width = null, // required
height = null, // required
resizable = false,
simulateClick = false,
clickOnTap = false,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true,
onResize = null,
touchAction = 'none',
@ -1094,7 +1096,8 @@ export class DOMScatter extends AbstractScatter {
this.height = height
this.throwVisibility = Math.min(width, height, throwVisibility)
this.container = container
this.simulateClick = simulateClick
this.clickOnTap = clickOnTap
this.triggerSVGClicks = triggerSVGClicks
this.scale = startScale
this.rotationDegrees = this.startRotationDegrees
this.transformOrigin = transformOrigin
@ -1107,7 +1110,8 @@ export class DOMScatter extends AbstractScatter {
rotation: this.startRotationDegrees,
transformOrigin: transformOrigin
}
this.tapNodes = new Map()
this.allowClickDistance = allowClickDistance
// For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues)
@ -1118,15 +1122,13 @@ export class DOMScatter extends AbstractScatter {
}
this.resizeButton = null
if (resizable) {
let button = document.createElement("div")
button.style.position = "absolute"
button.style.right = "0px"
button.style.bottom = "0px"
button.style.width = "50px";
button.style.height = "50px";
// button.style.borderRadius = "100% 0px 0px 0px";
// button.style.background = this.element.style.backgroundColor
button.className = "interactiveElement"
let button = document.createElement('div')
button.style.position = 'absolute'
button.style.right = '0px'
button.style.bottom = '0px'
button.style.width = '50px'
button.style.height = '50px'
button.className = 'interactiveElement'
this.element.appendChild(button)
button.addEventListener('pointerdown', (e) => {
@ -1142,6 +1144,25 @@ export class DOMScatter extends AbstractScatter {
})
this.resizeButton = button
}
if (clickOnTap) {
/* Since the tap triggers a synthetic click event
we must prevent the original trusted click event which
is also dispatched by the system.
*/
element.addEventListener('click', event => {
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.target.ownerSVGElement) {
if (this.triggerSVGClicks) {
return
}
}
else if (event.isTrusted) {
Events.stop(event)
}
}, true)
}
container.add(this)
}
@ -1301,45 +1322,109 @@ export class DOMScatter extends AbstractScatter {
TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ })
}
toggleVideo(element) {
if (element.paused) {
element.play()
} else {
element.pause()
onTap(event, interaction, point) {
if (this.clickOnTap) {
let directNode = document.elementFromPoint(event.clientX, event.clientY)
console.log("onTap", event)
if (this.isClickable(directNode)) {
directNode.click()
}
else {
let nearestNode = this.nearestClickable(event)
if (this.isClickable(nearestNode)) {
/* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a
proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page.
Therefore we use the original click event (see constructor).
*/
if (nearestNode.tagName == 'svg') {
let handler = this.tapNodes.get(nearestNode)
console.log("Clicking beneath SVG: to be done", handler)
if (this.triggerSVGClicks)
nearestNode.dispatchEvent(new Event('click'))
return
}
console.log("nearestNode clicked")
nearestNode.click()
}
}
}
}
onTap(event, interaction, point) {
if (this.simulateClick) {
let p = Points.fromPageToNode(this.element, point)
let iframe = this.element.querySelector('iframe')
if (iframe) {
let doc = iframe.contentWindow.document
let element = doc.elementFromPoint(p.x, p.y)
if (element == null) {
return
/**
* 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)
}
switch (element.tagName) {
case 'VIDEO':
console.log(element.currentSrc)
if (PopupMenu) {
PopupMenu.open(
{
Fullscreen: () =>
window.open(element.currentSrc),
Play: () => element.play()
},
{ x, y }
)
} else {
this.toggleVideo(element)
}
break
default:
element.click()
else {
InteractionMapper.on('tap', node, handler)
}
}
isClickable(node) {
if (node == null)
return false
if (node.tagName == 'A')
return true
if (node.hasAttribute("onclick"))
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) {
return closestClickable
}
return null
}
isDescendant(parent, child) {
@ -1378,42 +1463,37 @@ export class DOMScatter extends AbstractScatter {
}
resizeAfterTransform(zoom) {
// let w = this.width * this.scale
// let h = this.height * this.scale
// TweenLite.set(this.element, { width: w, height: h })
if (this.onResize) {
let w = this.width * this.scale
let h = this.height * this.scale
let event = new ResizeEvent(this, { width: w, height: h })
this.onResize(event)
}
if (this.resizeButton != null) {
// this.resizeButton.style.width = 50/this.scale+"px"
// this.resizeButton.style.height = 50/this.scale+"px"
}
}
startResize(e) {
e.preventDefault()
let event = new CustomEvent('resizeStarted')
let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }
this.bringToFront()
this.element.style.transformOrigin = "0% 0%"
this.element.style.transformOrigin = '0% 0%'
let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }
let offset = Points.subtract(oldPostition, newPostition)
this.oldX = e.clientX
this.oldY = e.clientY
e.target.setAttribute('resizing', "true")
e.target.setAttribute('resizing', 'true')
e.target.setPointerCapture(e.pointerId)
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } })
TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } })
TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } })
TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } })
this.element.dispatchEvent(event);
this.element.dispatchEvent(event)
}
resize(e) {
@ -1422,7 +1502,7 @@ export class DOMScatter extends AbstractScatter {
let rotation = Angle.radian2degree(this.rotation)
rotation = (rotation + 360) % 360
let event = new CustomEvent('resized')
if (e.target.getAttribute('resizing') == "true") {
if (e.target.getAttribute('resizing') == 'true') {
let deltaX = (e.clientX - this.oldX)
let deltaY = (e.clientY - this.oldY)
@ -1439,13 +1519,13 @@ export class DOMScatter extends AbstractScatter {
let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected))
let resizeH = -r * Math.sin(Angle.degree2radian(phiCorrected))
if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale });
if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale })
this.oldX = e.clientX
this.oldY = e.clientY
this.onResizing()
this.element.dispatchEvent(event);
this.element.dispatchEvent(event)
}
}
@ -1453,17 +1533,17 @@ export class DOMScatter extends AbstractScatter {
e.preventDefault()
let event = new CustomEvent('resizeEnded')
let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
this.element.style.transformOrigin = "50% 50%"
let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top};
let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }
this.element.style.transformOrigin = '50% 50%'
let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }
let offset = Points.subtract(oldPostition, newPostition)
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } })
TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } })
TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } })
TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } })
e.target.setAttribute('resizing', "false")
e.target.setAttribute('resizing', 'false')
this.element.dispatchEvent(event);
this.element.dispatchEvent(event)
}
}

View File

@ -402,6 +402,13 @@ export class Points {
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) {
// if (window.webkitConvertPointFromPageToNode) {
// return window.webkitConvertPointFromPageToNode(element,

85
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "iwmlib",
"version": "1.0.10",
"version": "1.0.15",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -184,9 +184,9 @@
}
},
"@types/pixi.js": {
"version": "4.8.7",
"resolved": "https://registry.npmjs.org/@types/pixi.js/-/pixi.js-4.8.7.tgz",
"integrity": "sha512-SuaeAVDWNbvVzg+ipVrNzVxMDZHaa/MRNT/+Y270sYp/qxfB31KC1wEt7KDVNq9Ac/pRdSDrxVDcLDUaYn0aVg=="
"version": "4.8.8",
"resolved": "https://registry.npmjs.org/@types/pixi.js/-/pixi.js-4.8.8.tgz",
"integrity": "sha512-5wmLnmL3foK/rqYMrrEM/3DxEwvwxJaP73RyqY8aMqq8zUm6CBlmc+12RIBH6iR/RHqU76XL238vWWJV1IN/zw=="
},
"acorn": {
"version": "5.7.3",
@ -194,9 +194,9 @@
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
},
"agent-base": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"requires": {
"es6-promisify": "^5.0.0"
}
@ -1049,9 +1049,9 @@
}
},
"es6-promise": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz",
"integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q=="
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-promisify": {
"version": "5.0.0",
@ -1430,13 +1430,20 @@
}
},
"fs-extra": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz",
"integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"requires": {
"graceful-fs": "^4.1.2",
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"dependencies": {
"graceful-fs": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
"integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg=="
}
}
},
"fs-mkdirp-stream": {
@ -2502,9 +2509,9 @@
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@ -2770,6 +2777,14 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"just-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz",
@ -2962,9 +2977,9 @@
}
},
"mime": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz",
"integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw=="
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
},
"mini-signals": {
"version": "1.2.0",
@ -3416,22 +3431,22 @@
"integrity": "sha1-i0tcQzsx5Bm8N53FZc4bg1qRs3I="
},
"pixi-particles": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.1.0.tgz",
"integrity": "sha512-By5470dTkHCTrM3T2xrFg9YRH6XK9BNpZjTZmWNzsHmyPbWB+4E/fg66b1DKFQ/vgez6xQm8wfHWcOpNrHWWHg=="
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.1.1.tgz",
"integrity": "sha512-R/vnqXzD2X4v4mSi3zJE81i1vGWaaZSDI/ImaZr8G4E0qBq2+OxB97Kb9WVWd7BlLFj4wR09VGKm7e5sQGQy4Q=="
},
"pixi-projection": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/pixi-projection/-/pixi-projection-0.2.7.tgz",
"integrity": "sha512-zrMUs2lDmPQX8AHXr14/MNGKXRlqr/XBjyI4Pm9fwz2sxMkz+QRLCa1weUrHXbfv1e6GjUe4tJ8KgqeNuRkc3w==",
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/pixi-projection/-/pixi-projection-0.2.8.tgz",
"integrity": "sha512-3K06VHVDNm0rnCd72HhH9cZF/davWZXIMcJMFzwAuIV9Io2ExQrUv3Eauri4A682jsoriM+stHgm+6n75Ps5lg==",
"requires": {
"@types/pixi.js": "^4.7.1"
}
},
"pixi.js": {
"version": "4.8.7",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.7.tgz",
"integrity": "sha512-mx7YbHPkkWoj8FT3qBMkieAjBuuJ4yZWU7rq9NnCSUGpNrVlocrW179xrJQPVR2Q7JZ73ZGTwH7NOUZ9wgh7wA==",
"version": "4.8.8",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.8.tgz",
"integrity": "sha512-wQzuLAWSMfV+x2guL5jZBp37pwCmYXHiTXG7ZWXu4E/5IsC9xozwmOfLeCNEyPzlyucOgxAx/HS+tLqxWPYX7Q==",
"requires": {
"bit-twiddle": "^1.0.2",
"earcut": "^2.1.4",
@ -3501,9 +3516,9 @@
}
},
"puppeteer": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.17.0.tgz",
"integrity": "sha512-3EXZSximCzxuVKpIHtyec8Wm2dWZn1fc5tQi34qWfiUgubEVYHjUvr0GOJojqf3mifI6oyKnCdrGxaOI+lWReA==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.18.1.tgz",
"integrity": "sha512-luUy0HPSuWPsPZ1wAp6NinE0zgetWtudf5zwZ6dHjMWfYpTQcmKveFRox7VBNhQ98OjNA9PQ9PzQyX8k/KrxTg==",
"requires": {
"debug": "^4.1.0",
"extract-zip": "^1.6.6",
@ -3524,9 +3539,9 @@
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},

View File

@ -34,10 +34,10 @@
"optimal-select": "^4.0.1",
"pixi-compressed-textures": "^1.1.8",
"pixi-filters": "^2.7.1",
"pixi-particles": "^4.1.0",
"pixi-projection": "^0.2.7",
"pixi.js": "^4.8.7",
"pixi-particles": "^4.1.1",
"pixi-projection": "^0.2.8",
"pixi.js": "^4.8.8",
"propagating-hammerjs": "^1.4.6",
"puppeteer": "^1.16.0"
"puppeteer": "^1.18.1"
}
}

View File

@ -19,4 +19,4 @@ export default [{
watch: {
clearScreen: false
}
}];
}]