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 # own
*.code-workspace *.code-workspace
.history/ .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 html {
{ padding: 0;
padding: 0px; font-size: 16px;
font-size: 16px; background: white;
background: white; font-family: Arial, sans-serif;
font-family: Arial,sans-serif; color: #000;
color: #000; max-width: 932px;
max-width: 932px; margin: 0 auto;
margin:0 auto;
} }
.grayBorder { .grayBorder {
@ -24,10 +23,10 @@ html
} }
.unselectable { .unselectable {
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
} }
body { body {
@ -44,28 +43,27 @@ canvas {
margin-left: 8px; margin-left: 8px;
} }
.intrinsic-container { .intrinsic-container {
position: relative; position: relative;
height: 0; height: 0;
overflow: hidden; overflow: hidden;
} }
/* 16x9 Aspect Ratio */ /* 16x9 Aspect Ratio */
.intrinsic-container-16x9 { .intrinsic-container-16x9 {
padding-bottom: 56.25%; padding-bottom: 56.25%;
} }
/* 4x3 Aspect Ratio */ /* 4x3 Aspect Ratio */
.intrinsic-container-4x3 { .intrinsic-container-4x3 {
padding-bottom: 75%; padding-bottom: 75%;
} }
.intrinsic-container iframe { .intrinsic-container iframe {
position: absolute; position: absolute;
border: 0; border: 0;
top:0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View File

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

View File

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

4559
dist/iwmlib.js vendored

File diff suppressed because it is too large Load Diff

410
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,
@ -4781,7 +4788,7 @@
} }
} }
/* globals Hammer, propagating */ /* eslint-disable no-unused-vars */
/** Interaction patterns /** Interaction patterns
@ -4789,6 +4796,7 @@
*/ */
class IInteractionTarget extends Interface { class IInteractionTarget extends Interface {
capture(event) { capture(event) {
return typeof true return typeof true
} }
@ -4992,10 +5000,10 @@
let d1 = Points.subtract(c1, p1); let d1 = Points.subtract(c1, p1);
let d2 = Points.subtract(c2, p2); let d2 = Points.subtract(c2, p2);
let cm = Points.mean(c1, c2); let cm = Points.mean(c1, c2);
// Using the mean leads to jumps between time slices with 3 and 2 fingers // Using the mean leads to jumps between time slices with 3 and 2 fingers
// We use the mean of deltas instead // We use the mean of deltas instead
let delta = Points.mean(d1, d2); let delta = Points.mean(d1, d2);
let zoom = 1.0; let zoom = 1.0;
let distance1 = Points.distance(p1, p2); let distance1 = Points.distance(p1, p2);
let distance2 = Points.distance(c1, c2); let distance2 = Points.distance(c1, c2);
@ -5199,7 +5207,6 @@
} }
let result = false; let result = false;
if (this.isTap(key)) { if (this.isTap(key)) {
this.registerTap(key, ended); this.registerTap(key, ended);
result = this.tapCounts.get(key) == 2; result = this.tapCounts.get(key) == 2;
} }
@ -5313,7 +5320,9 @@
if (this.capturePointerEvents) { if (this.capturePointerEvents) {
try { try {
element.setPointerCapture(e.pointerId); element.setPointerCapture(e.pointerId);
} catch (e) { } } catch (e) {
console.warn('Cannot setPointerCapture');
}
} }
this.onStart(e); this.onStart(e);
} }
@ -5345,7 +5354,9 @@
if (this.capturePointerEvents) { if (this.capturePointerEvents) {
try { try {
element.releasePointerCapture(e.pointerId); element.releasePointerCapture(e.pointerId);
} catch (e) { } } catch (e) {
console.warn('Cannot release pointer');
}
} }
}, },
useCapture useCapture
@ -5389,7 +5400,7 @@
e => { e => {
if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target); if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target);
if (e.target == element) { if (e.target == element) {
this.onEnd(e); this.onEnd(e);
} }
}, },
useCapture); useCapture);
@ -5492,9 +5503,8 @@
e => { e => {
if (e.target == element) { if (e.target == element) {
this.onEnd(e); this.onEnd(e);
console.warn("Shouldn't happen: mouseout ends interaction"); console.warn('Shouldn\'t happen: mouseout ends interaction');
} }
}, },
useCapture useCapture
); );
@ -5594,39 +5604,42 @@
// 'targetTouches' // 'targetTouches'
let result = {}; let result = {};
switch (event.constructor.name) { switch (event.constructor.name) {
case 'MouseEvent': case 'MouseEvent': {
let buttons = event.buttons || event.which; let buttons = event.buttons || event.which;
if (buttons) result['mouse'] = this.getPosition(event); if (buttons) result['mouse'] = this.getPosition(event);
break break
case 'PointerEvent': }
result[event.pointerId.toString()] = this.getPosition(event); case 'PointerEvent': {
break result[event.pointerId.toString()] = this.getPosition(event);
case 'Touch': break
let id = }
case 'Touch': {
let id =
event.touchType === 'stylus' event.touchType === 'stylus'
? 'stylus' ? 'stylus'
: event.identifier.toString(); : event.identifier.toString();
result[id] = this.getPosition(event); result[id] = this.getPosition(event);
break break
// case 'TouchEvent': }
// // Needs to be observed: Perhaps changedTouches are all we need. If so // case 'TouchEvent':
// // we can remove the touchEventKey default parameter // // Needs to be observed: Perhaps changedTouches are all we need. If so
// if (touchEventKey == 'all') { // // we can remove the touchEventKey default parameter
// for(let t of event.targetTouches) { // if (touchEventKey == 'all') {
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.targetTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// for(let t of event.changedTouches) { // }
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.changedTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// } // }
// else { // }
// for(let t of event.changedTouches) { // else {
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.changedTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// } // }
// break // }
default: // break
break default:
break
} }
return result return result
} }
@ -5654,7 +5667,7 @@
let point = extracted[key]; let point = extracted[key];
let updated = this.interaction.update(key, point); let updated = this.interaction.update(key, point);
if (updated) { 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); this.interactionStarted(event, key, point);
} }
} }
@ -5805,6 +5818,7 @@
* @param {object} [opts] - An options object. See the hammer documentation for more details. * @param {object} [opts] - An options object. See the hammer documentation for more details.
*/ */
static on(types, elements, cb, opts = {}) { static on(types, elements, cb, opts = {}) {
opts = Object.assign({}, { opts = Object.assign({}, {
}, opts); }, opts);
@ -6039,6 +6053,11 @@
window.Capabilities = Capabilities; window.Capabilities = Capabilities;
window.CapabilitiesTests = CapabilitiesTests; 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. * A base class for scatter specific events.
* *
@ -6088,7 +6107,7 @@
toString() { toString() {
return ( return (
"Event('scatterTransformed', scale: " + 'Event(\'scatterTransformed\', scale: ' +
this.scale + this.scale +
' about: ' + ' about: ' +
this.about.x + this.about.x +
@ -6163,7 +6182,7 @@
// Avoid division by zero errors later on // Avoid division by zero errors later on
// and consider the number of involved pointers sind addVelocity will be called by the // and consider the number of involved pointers sind addVelocity will be called by the
// onMove events // 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); this.velocities.push(velocity);
while (this.velocities.length > buffer) { while (this.velocities.length > buffer) {
this.velocities.shift(); this.velocities.shift();
@ -6302,7 +6321,8 @@
onThrowFinished = null, onThrowFinished = null,
scaleAutoClose = false, scaleAutoClose = false,
scaleCloseThreshold = 0.10, scaleCloseThreshold = 0.10,
scaleCloseBuffer = 0.05 scaleCloseBuffer = 0.05,
maxRotation = Angle.degree2radian(5)
} = {}) { } = {}) {
if (rotationDegrees != null && rotation != null) { if (rotationDegrees != null && rotation != null) {
throw new Error('Use rotationDegrees or rotation but not both') throw new Error('Use rotationDegrees or rotation but not both')
@ -6334,6 +6354,7 @@
this.startScale = startScale; // Needed to reset object this.startScale = startScale; // Needed to reset object
this.minScale = minScale; this.minScale = minScale;
this.maxScale = maxScale; this.maxScale = maxScale;
this.maxRotation = maxRotation;
this.overdoScaling = overdoScaling; this.overdoScaling = overdoScaling;
this.translatable = translatable; this.translatable = translatable;
if (!translatable) { if (!translatable) {
@ -6345,6 +6366,7 @@
this.resizable = resizable; this.resizable = resizable;
this.mouseZoomFactor = mouseZoomFactor; this.mouseZoomFactor = mouseZoomFactor;
this.autoBringToFront = autoBringToFront; this.autoBringToFront = autoBringToFront;
this.dragging = false; this.dragging = false;
this.onTransform = onTransform != null ? [onTransform] : null; this.onTransform = onTransform != null ? [onTransform] : null;
this.onClose = onClose != null ? [onClose] : null; this.onClose = onClose != null ? [onClose] : null;
@ -6379,10 +6401,15 @@
gesture(interaction) { gesture(interaction) {
let delta = interaction.delta(); let delta = interaction.delta();
//console.log("gesture", delta)
if (delta != null) { if (delta != null) {
this.addVelocity(delta); 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; if (delta.zoom != 1) this.interactionAnchor = delta.about;
} }
} }
@ -6451,7 +6478,7 @@
let stagePolygon = this.containerPolygon; let stagePolygon = this.containerPolygon;
// UO: since keepOnStage is called in nextVelocity we need to // UO: since keepOnStage is called in nextVelocity we need to
// ensure a return value // ensure a return value
if (!stagePolygon) return { x: 0, y: 0} if (!stagePolygon) return { x: 0, y: 0 }
let polygon = this.polygon; let polygon = this.polygon;
let bounced = this.bouncing(); let bounced = this.bouncing();
if (bounced) { if (bounced) {
@ -6735,20 +6762,6 @@
} }
this._updateTransparency(); 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) { onStart(event, interaction) {
@ -6915,7 +6928,9 @@
width = null, // required width = null, // required
height = null, // required height = null, // required
resizable = false, resizable = false,
simulateClick = false, clickOnTap = false,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true, verbose = true,
onResize = null, onResize = null,
touchAction = 'none', touchAction = 'none',
@ -6966,7 +6981,8 @@
this.height = height; this.height = height;
this.throwVisibility = Math.min(width, height, throwVisibility); this.throwVisibility = Math.min(width, height, throwVisibility);
this.container = container; this.container = container;
this.simulateClick = simulateClick; this.clickOnTap = clickOnTap;
this.triggerSVGClicks = triggerSVGClicks;
this.scale = startScale; this.scale = startScale;
this.rotationDegrees = this.startRotationDegrees; this.rotationDegrees = this.startRotationDegrees;
this.transformOrigin = transformOrigin; this.transformOrigin = transformOrigin;
@ -6979,7 +6995,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);
@ -6990,15 +7007,13 @@
} }
this.resizeButton = null; this.resizeButton = null;
if (resizable) { if (resizable) {
let button = document.createElement("div"); let button = document.createElement('div');
button.style.position = "absolute"; button.style.position = 'absolute';
button.style.right = "0px"; button.style.right = '0px';
button.style.bottom = "0px"; button.style.bottom = '0px';
button.style.width = "50px"; button.style.width = '50px';
button.style.height = "50px"; button.style.height = '50px';
// button.style.borderRadius = "100% 0px 0px 0px"; button.className = 'interactiveElement';
// button.style.background = this.element.style.backgroundColor
button.className = "interactiveElement";
this.element.appendChild(button); this.element.appendChild(button);
button.addEventListener('pointerdown', (e) => { button.addEventListener('pointerdown', (e) => {
@ -7014,6 +7029,25 @@
}); });
this.resizeButton = button; 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); container.add(this);
} }
@ -7173,47 +7207,111 @@
TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ });
} }
toggleVideo(element) {
if (element.paused) {
element.play();
} else {
element.pause();
}
}
onTap(event, interaction, point) { onTap(event, interaction, point) {
if (this.simulateClick) {
let p = Points.fromPageToNode(this.element, point); if (this.clickOnTap) {
let iframe = this.element.querySelector('iframe'); let directNode = document.elementFromPoint(event.clientX, event.clientY);
if (iframe) { console.log("onTap", event);
let doc = iframe.contentWindow.document; if (this.isClickable(directNode)) {
let element = doc.elementFromPoint(p.x, p.y); directNode.click();
if (element == null) { }
return else {
} let nearestNode = this.nearestClickable(event);
switch (element.tagName) { if (this.isClickable(nearestNode)) {
case 'VIDEO': /* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a
console.log(element.currentSrc); proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page.
if (PopupMenu) { Therefore we use the original click event (see constructor).
PopupMenu.open( */
{ if (nearestNode.tagName == 'svg') {
Fullscreen: () => let handler = this.tapNodes.get(nearestNode);
window.open(element.currentSrc), console.log("Clicking beneath SVG: to be done", handler);
Play: () => element.play() if (this.triggerSVGClicks)
}, nearestNode.dispatchEvent(new Event('click'));
{ x, y } return
); }
} else { console.log("nearestNode clicked");
this.toggleVideo(element); nearestNode.click();
}
break
default:
element.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 == 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) { isDescendant(parent, child) {
let node = child.parentNode; let node = child.parentNode;
while (node != null) { while (node != null) {
@ -7250,37 +7348,35 @@
} }
resizeAfterTransform(zoom) { 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) { if (this.onResize) {
let w = this.width * this.scale;
let h = this.height * this.scale;
let event = new ResizeEvent(this, { width: w, height: h }); let event = new ResizeEvent(this, { width: w, height: h });
this.onResize(event); this.onResize(event);
} }
if (this.resizeButton != null) ;
} }
startResize(e) { startResize(e) {
e.preventDefault(); e.preventDefault();
let event = new CustomEvent('resizeStarted'); 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.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); let offset = Points.subtract(oldPostition, newPostition);
this.oldX = e.clientX; this.oldX = e.clientX;
this.oldY = e.clientY; this.oldY = e.clientY;
e.target.setAttribute('resizing', "true"); e.target.setAttribute('resizing', 'true');
e.target.setPointerCapture(e.pointerId); e.target.setPointerCapture(e.pointerId);
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); 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: { top: '+=' + offset.y + 'px' } });
this.element.dispatchEvent(event); this.element.dispatchEvent(event);
} }
@ -7291,7 +7387,7 @@
let rotation = Angle.radian2degree(this.rotation); let rotation = Angle.radian2degree(this.rotation);
rotation = (rotation + 360) % 360; rotation = (rotation + 360) % 360;
let event = new CustomEvent('resized'); let event = new CustomEvent('resized');
if (e.target.getAttribute('resizing') == "true") { if (e.target.getAttribute('resizing') == 'true') {
let deltaX = (e.clientX - this.oldX); let deltaX = (e.clientX - this.oldX);
let deltaY = (e.clientY - this.oldY); let deltaY = (e.clientY - this.oldY);
@ -7308,7 +7404,7 @@
let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected)); let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected));
let resizeH = -r * Math.sin(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.oldX = e.clientX;
this.oldY = e.clientY; this.oldY = e.clientY;
@ -7322,15 +7418,15 @@
e.preventDefault(); e.preventDefault();
let event = new CustomEvent('resizeEnded'); let event = new CustomEvent('resizeEnded');
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.element.style.transformOrigin = "50% 50%"; this.element.style.transformOrigin = '50% 50%';
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); let offset = Points.subtract(oldPostition, newPostition);
TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); 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: { top: '+=' + offset.y + 'px' } });
e.target.setAttribute('resizing', "false"); e.target.setAttribute('resizing', 'false');
this.element.dispatchEvent(event); this.element.dispatchEvent(event);
} }
@ -7367,12 +7463,12 @@
this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight; this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight;
this.addedNode = null; this.addedNode = null;
console.log({ console.log({
width, width,
height, height,
maxWidth, maxWidth,
maxHeight, maxHeight,
}); });
} }
@ -7401,6 +7497,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,
@ -7420,6 +7517,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;
@ -7474,7 +7572,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
} }
); );
@ -7483,7 +7582,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();
@ -7498,11 +7596,7 @@
scatter.onTransform.splice(callbackIdx, 1); scatter.onTransform.splice(callbackIdx, 1);
} }
} }
}.bind(this); }.bind(this);
scatter.addTransformEventCallback(removeOnMinScale); scatter.addTransformEventCallback(removeOnMinScale);
} }
@ -7529,6 +7623,7 @@
} }
setupFlippable(flippable, loader) { setupFlippable(flippable, loader) {
console.log("setupFlippable", loader.wantedWidth);
flippable.wantedWidth = loader.wantedWidth; flippable.wantedWidth = loader.wantedWidth;
flippable.wantedHeight = loader.wantedHeight; flippable.wantedHeight = loader.wantedHeight;
flippable.wantedScale = loader.scale; flippable.wantedScale = loader.scale;
@ -7538,8 +7633,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;
@ -7588,10 +7686,16 @@
this.onRemoved = flip.onRemoved; this.onRemoved = flip.onRemoved;
this.onUpdate = flip.onUpdate; 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.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 });
@ -7604,16 +7708,22 @@
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 => {
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 => {
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();
@ -7663,18 +7773,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
}); });
@ -7687,6 +7785,7 @@
clickInfo() { clickInfo() {
this.bringToFront(); this.bringToFront();
console.log("clickInfo");
this.infoBtn.click(); this.infoBtn.click();
} }
@ -7728,8 +7827,6 @@
} }
} }
enable(button) { enable(button) {
this.show(button, this.fadeDuration); this.show(button, this.fadeDuration);
if (button) { if (button) {
@ -7785,15 +7882,15 @@
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("start", this.flipDuration);
TweenLite.to(this.card, this.flipDuration, { TweenLite.to(this.card, this.flipDuration, {
rotationY: targetY, rotationY: targetY,
ease: Power1.easeOut, ease: Power1.easeOut,
transformOrigin: '50% 50%', transformOrigin: '50% 50%',
onUpdate, onUpdate,
onComplete: e => { onComplete: e => {
console.log("start end", this.flipDuration);
if (this.flipped) { if (this.flipped) {
//this.hide(this.front) //this.hide(this.front)
this.enable(this.backBtn); this.enable(this.backBtn);
@ -7826,6 +7923,7 @@
force3D: true force3D: true
}); });
console.log("start 2", this.wantedWidth, this.startWidth, {w, h});
// See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/ // See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/
TweenLite.to(this.element, this.flipDuration / 2, { TweenLite.to(this.element, this.flipDuration / 2, {
scale: targetScale, scale: targetScale,

View File

@ -66,13 +66,13 @@ templates.
</main> </main>
<script class="doctest"> <script class="doctest">
let scatterContainer = new DOMScatterContainer(main) let scatterContainer = new DOMScatterContainer(main, {stopEvents: false})
if (Capabilities.supportsTemplate()) { if (Capabilities.supportsTemplate()) {
let flip = new DOMFlip(scatterContainer, let flip = new DOMFlip(scatterContainer,
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})
}) })

View File

@ -31,12 +31,12 @@ export class CardLoader {
this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight
this.addedNode = null this.addedNode = null
console.log({ console.log({
width, width,
height, height,
maxWidth, maxWidth,
maxHeight, maxHeight,
}) })
} }
@ -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)
} }
@ -327,6 +325,7 @@ export class DOMFlip {
} }
setupFlippable(flippable, loader) { setupFlippable(flippable, loader) {
console.log("setupFlippable", loader.wantedWidth)
flippable.wantedWidth = loader.wantedWidth flippable.wantedWidth = loader.wantedWidth
flippable.wantedHeight = loader.wantedHeight flippable.wantedHeight = loader.wantedHeight
flippable.wantedScale = loader.scale flippable.wantedScale = loader.scale
@ -336,8 +335,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
@ -386,10 +388,16 @@ export class DOMFlippable {
this.onRemoved = flip.onRemoved this.onRemoved = flip.onRemoved
this.onUpdate = flip.onUpdate 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.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,16 +410,22 @@ 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) {
InteractionMapper.on('tap', this.infoBtn, event => this.flip.start())
if (this.infoBtn) {
scatter.addTapListener(this.infoBtn, event => {
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 => {
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()
@ -461,18 +475,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
}) })
@ -485,6 +487,7 @@ export class DOMFlippable {
clickInfo() { clickInfo() {
this.bringToFront() this.bringToFront()
console.log("clickInfo")
this.infoBtn.click() this.infoBtn.click()
} }
@ -526,8 +529,6 @@ export class DOMFlippable {
} }
} }
enable(button) { enable(button) {
this.show(button, this.fadeDuration) this.show(button, this.fadeDuration)
if (button) { if (button) {
@ -583,15 +584,15 @@ 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("start", this.flipDuration)
TweenLite.to(this.card, this.flipDuration, { TweenLite.to(this.card, this.flipDuration, {
rotationY: targetY, rotationY: targetY,
ease: Power1.easeOut, ease: Power1.easeOut,
transformOrigin: '50% 50%', transformOrigin: '50% 50%',
onUpdate, onUpdate,
onComplete: e => { onComplete: e => {
console.log("start end", this.flipDuration)
if (this.flipped) { if (this.flipped) {
//this.hide(this.front) //this.hide(this.front)
this.enable(this.backBtn) this.enable(this.backBtn)
@ -624,6 +625,7 @@ export class DOMFlippable {
force3D: true force3D: true
}) })
console.log("start 2", this.wantedWidth, this.startWidth, {w, h})
// See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/ // See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/
TweenLite.to(this.element, this.flipDuration / 2, { TweenLite.to(this.element, this.flipDuration / 2, {
scale: targetScale, scale: targetScale,

View File

@ -1,8 +1,9 @@
/* eslint-disable no-unused-vars */
/* globals Hammer, propagating */ /* globals Hammer, propagating */
/*eslint no-console: ["error", { allow: ["log", "warn", "info", "error"] }]*/ /*eslint no-console: ["error", { allow: ["log", "warn", "info", "error"] }]*/
import Interface from './interface.js' import Interface from './interface.js'
import { Points, Angle, MapProxy } from './utils.js' import { Points, MapProxy } from './utils.js'
import Events from './events.js' import Events from './events.js'
import Logging from './logging.js' import Logging from './logging.js'
@ -12,6 +13,7 @@ import Logging from './logging.js'
*/ */
export class IInteractionTarget extends Interface { export class IInteractionTarget extends Interface {
capture(event) { capture(event) {
return typeof true return typeof true
} }
@ -215,10 +217,10 @@ export class InteractionPoints {
let d1 = Points.subtract(c1, p1) let d1 = Points.subtract(c1, p1)
let d2 = Points.subtract(c2, p2) let d2 = Points.subtract(c2, p2)
let cm = Points.mean(c1, c2) let cm = Points.mean(c1, c2)
// Using the mean leads to jumps between time slices with 3 and 2 fingers // Using the mean leads to jumps between time slices with 3 and 2 fingers
// We use the mean of deltas instead // We use the mean of deltas instead
let delta = Points.mean(d1, d2) let delta = Points.mean(d1, d2)
let zoom = 1.0 let zoom = 1.0
let distance1 = Points.distance(p1, p2) let distance1 = Points.distance(p1, p2)
let distance2 = Points.distance(c1, c2) let distance2 = Points.distance(c1, c2)
@ -422,7 +424,6 @@ export class Interaction extends InteractionPoints {
} }
let result = false let result = false
if (this.isTap(key)) { if (this.isTap(key)) {
this.registerTap(key, ended) this.registerTap(key, ended)
result = this.tapCounts.get(key) == 2 result = this.tapCounts.get(key) == 2
} }
@ -536,7 +537,9 @@ export class InteractionDelegate {
if (this.capturePointerEvents) { if (this.capturePointerEvents) {
try { try {
element.setPointerCapture(e.pointerId) element.setPointerCapture(e.pointerId)
} catch (e) { } } catch (e) {
console.warn('Cannot setPointerCapture')
}
} }
this.onStart(e) this.onStart(e)
} }
@ -568,7 +571,9 @@ export class InteractionDelegate {
if (this.capturePointerEvents) { if (this.capturePointerEvents) {
try { try {
element.releasePointerCapture(e.pointerId) element.releasePointerCapture(e.pointerId)
} catch (e) { } } catch (e) {
console.warn('Cannot release pointer')
}
} }
}, },
useCapture useCapture
@ -612,7 +617,7 @@ export class InteractionDelegate {
e => { e => {
if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target) if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target)
if (e.target == element) { if (e.target == element) {
this.onEnd(e) this.onEnd(e)
} }
}, },
useCapture) useCapture)
@ -715,9 +720,8 @@ export class InteractionDelegate {
e => { e => {
if (e.target == element) { if (e.target == element) {
this.onEnd(e) this.onEnd(e)
console.warn("Shouldn't happen: mouseout ends interaction") console.warn('Shouldn\'t happen: mouseout ends interaction')
} }
}, },
useCapture useCapture
) )
@ -819,39 +823,42 @@ export class InteractionDelegate {
// 'targetTouches' // 'targetTouches'
let result = {} let result = {}
switch (event.constructor.name) { switch (event.constructor.name) {
case 'MouseEvent': case 'MouseEvent': {
let buttons = event.buttons || event.which let buttons = event.buttons || event.which
if (buttons) result['mouse'] = this.getPosition(event) if (buttons) result['mouse'] = this.getPosition(event)
break break
case 'PointerEvent': }
result[event.pointerId.toString()] = this.getPosition(event) case 'PointerEvent': {
break result[event.pointerId.toString()] = this.getPosition(event)
case 'Touch': break
let id = }
case 'Touch': {
let id =
event.touchType === 'stylus' event.touchType === 'stylus'
? 'stylus' ? 'stylus'
: event.identifier.toString() : event.identifier.toString()
result[id] = this.getPosition(event) result[id] = this.getPosition(event)
break break
// case 'TouchEvent': }
// // Needs to be observed: Perhaps changedTouches are all we need. If so // case 'TouchEvent':
// // we can remove the touchEventKey default parameter // // Needs to be observed: Perhaps changedTouches are all we need. If so
// if (touchEventKey == 'all') { // // we can remove the touchEventKey default parameter
// for(let t of event.targetTouches) { // if (touchEventKey == 'all') {
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.targetTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// for(let t of event.changedTouches) { // }
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.changedTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// } // }
// else { // }
// for(let t of event.changedTouches) { // else {
// result[t.identifier.toString()] = this.getPosition(t) // for(let t of event.changedTouches) {
// } // result[t.identifier.toString()] = this.getPosition(t)
// } // }
// break // }
default: // break
break default:
break
} }
return result return result
} }
@ -879,7 +886,7 @@ export class InteractionDelegate {
let point = extracted[key] let point = extracted[key]
let updated = this.interaction.update(key, point) let updated = this.interaction.update(key, point)
if (updated) { 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) 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. * @param {object} [opts] - An options object. See the hammer documentation for more details.
*/ */
static on(types, elements, cb, opts = {}) { static on(types, elements, cb, opts = {}) {
opts = Object.assign({}, { opts = Object.assign({}, {
}, opts) }, opts)

View File

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

85
package-lock.json generated
View File

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

View File

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

View File

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