Removed setup from constructor to simplify specializations
This commit is contained in:
+2
-2
@@ -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})
|
||||
})
|
||||
|
||||
+33
-31
@@ -31,12 +31,12 @@ export class CardLoader {
|
||||
this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight
|
||||
this.addedNode = null
|
||||
console.log({
|
||||
|
||||
|
||||
width,
|
||||
height,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+48
-40
@@ -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
|
||||
}
|
||||
@@ -215,10 +217,10 @@ export class InteractionPoints {
|
||||
let d1 = Points.subtract(c1, p1)
|
||||
let d2 = Points.subtract(c2, p2)
|
||||
let cm = Points.mean(c1, c2)
|
||||
|
||||
|
||||
// Using the mean leads to jumps between time slices with 3 and 2 fingers
|
||||
// We use the mean of deltas instead
|
||||
let delta = Points.mean(d1, d2)
|
||||
let delta = Points.mean(d1, d2)
|
||||
let zoom = 1.0
|
||||
let distance1 = Points.distance(p1, p2)
|
||||
let distance2 = Points.distance(c1, c2)
|
||||
@@ -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
|
||||
@@ -612,7 +617,7 @@ export class InteractionDelegate {
|
||||
e => {
|
||||
if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target)
|
||||
if (e.target == element) {
|
||||
this.onEnd(e)
|
||||
this.onEnd(e)
|
||||
}
|
||||
},
|
||||
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,39 +823,42 @@ export class InteractionDelegate {
|
||||
// 'targetTouches'
|
||||
let result = {}
|
||||
switch (event.constructor.name) {
|
||||
case 'MouseEvent':
|
||||
let buttons = event.buttons || event.which
|
||||
if (buttons) result['mouse'] = this.getPosition(event)
|
||||
break
|
||||
case 'PointerEvent':
|
||||
result[event.pointerId.toString()] = this.getPosition(event)
|
||||
break
|
||||
case 'Touch':
|
||||
let id =
|
||||
case 'MouseEvent': {
|
||||
let buttons = event.buttons || event.which
|
||||
if (buttons) result['mouse'] = this.getPosition(event)
|
||||
break
|
||||
}
|
||||
case 'PointerEvent': {
|
||||
result[event.pointerId.toString()] = this.getPosition(event)
|
||||
break
|
||||
}
|
||||
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
|
||||
// if (touchEventKey == 'all') {
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// for(let t of event.changedTouches) {
|
||||
// result[t.identifier.toString()] = this.getPosition(t)
|
||||
// }
|
||||
// }
|
||||
// break
|
||||
default:
|
||||
break
|
||||
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
|
||||
// if (touchEventKey == 'all') {
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// for(let t of event.changedTouches) {
|
||||
// result[t.identifier.toString()] = this.getPosition(t)
|
||||
// }
|
||||
// }
|
||||
// break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+46
-4
@@ -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>
|
||||
|
||||
+183
-103
@@ -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,47 +1322,111 @@ 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.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
|
||||
}
|
||||
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()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let node = child.parentNode
|
||||
while (node != null) {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user