React Konva move screen with two fingers on touchpad - react-konva

I'm looking for the correct event to navigate on the canvas with two fingers on touch pad. I'm using React Konva.js and I found a good example on the site https://konvajs.org/docs/sandbox/Zooming_Relative_To_Pointer.html
The problem that I don't want to zoom in or out with the two fingers but navigate. Does anyone have a relevant example?

const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const shape = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 50,
fill: 'green'
});
layer.add(shape);
stage.on('wheel', (e) => {
const dx = -e.evt.deltaX;
const dy = -e.evt.deltaY;
stage.x(stage.x() + dx);
stage.y(stage.y() + dy);
})
body {
padding: 0;
margin: 0;
}
<script src="https://unpkg.com/konva#^8/konva.min.js"></script>
<div id="container"></div>

To complete the great answer by #lavrton, you can add zoom trackpad support with:
window.addEventListener('wheel', (e) => {
e.preventDefault();
if (e.ctrlKey) { // special flag that's active while zooming
scale -= e.deltaY * 0.01;
} else {
posX -= e.deltaX * 2;
posY -= e.deltaY * 2;
}
render();
});
// gesture event with rotation is Safari only
window.addEventListener("gesturestart", function (e) {
e.preventDefault();
startX = e.pageX - posX;
startY = e.pageY - posY;
gestureStartRotation = rotation;
gestureStartScale = scale;
});
window.addEventListener("gesturechange", function (e) {
e.preventDefault();
rotation = gestureStartRotation + e.rotation;
scale = gestureStartScale * e.scale;
posX = e.pageX - startX;
posY = e.pageY - startY;
render();
})
window.addEventListener("gestureend", function (e) {
e.preventDefault();
});
https://stackblitz.com/edit/multi-touch-trackpad-gesture?file=index.js
From: https://kenneth.io/post/detecting-multi-touch-trackpad-gestures-in-javascript
Would be nice to have it on the offical konva.js example site.

Related

Add animated PNG. to polyline in google maps API

I'm trying to animate a icon over a polyline like in this example: https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-symbol-animate.
It works but I want to add my own PNG as the icon that is being animated. This is what I tried:
var line = new google.maps.Polyline({
path: [{ lat: 25.774266, lng: -80.193659 }, { lat: 25.83333, lng: 25.83333 - 77.8999964 }],
icons: [{
icon: CruiseShip //This is my .PNG file
}],
strokeColor: '#ffffff',
strokeWeight: 1,
map: map
});
function animateCircle(line) {
var count = 0;
window.setInterval(function () {
count = (count + 1) % 200;
var icons = line.get('icons');
icons[0].offset = (count / 2) + '%';
line.set('icons', icons);
}, 20);
}
Using the lineSymbol object like in the example does work. How can I add a .PNG file to the polyline instead of the lineSymbol? Couldn't find any documentation on this.
The way you are trying to animate the icon only works for SVG Symbols.
Mike Williams wrote an extension to the Google Maps Javascript API v2 (now deprecated and turned off) called epoly which contains the method .GetPointAtDistance, which can be used to update the position of a "normal" icon along a polyline.
There is a interpolate method in the google.maps.geometry.spherical library which interpolates between two points, but isn't very accurate for large scale features.
proof of concept fiddle
code snippet:
// This example creates a 2-pixel-wide red polyline showing the path of
// the first trans-Pacific flight between Oakland, CA, and Brisbane,
// Australia which was made by Charles Kingsford Smith.
function initMap() {
updatePolylinePrototype();
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 3,
center: {
lat: 0,
lng: -180
},
mapTypeId: 'terrain'
});
var line = new google.maps.Polyline({
path: [{
lat: 25.774266,
lng: -80.193659
}, {
lat: 25.83333,
lng: 25.83333 - 77.8999964
}, {
lat: 28.411413,
lng: -16.5449611
}],
strokeColor: '#ffffff',
strokeWeight: 1,
map: map
});
var marker = new google.maps.Marker({
icon: {
url: "http://earth.google.com/images/kml-icons/track-directional/track-12.png",
anchor: new google.maps.Point(12, 12),
scaledSize: new google.maps.Size(24, 24),
},
position: line.getPath().getAt(0),
map: map
})
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < line.getPath().getLength(); i++) {
bounds.extend(line.getPath().getAt(i));
}
map.fitBounds(bounds);
animateShip(line, marker);
}
function animateShip(line, marker) {
var count = 0;
var lineDistance = 0;
for (var i = 1; i < line.getPath().getLength(); i++) {
lineDistance += google.maps.geometry.spherical.computeDistanceBetween(line.getPath().getAt(i - 1), line.getPath().getAt(i))
}
window.setInterval(function() {
count = (count + 1) % 200;
marker.setPosition(line.GetPointAtDistance(lineDistance - (lineDistance * count / 200)));
}, 20);
}
function updatePolylinePrototype() {
// === A method which returns a GLatLng of a point a given distance along the path ===
// === Returns null if the path is shorter than the specified distance ===
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist = 0;
var olddist = 0;
for (var i = 1;
(i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += google.maps.geometry.spherical.computeDistanceBetween(this.getPath().getAt(i), this.getPath().getAt(i - 1));
}
if (dist < metres) {
return null;
}
var p1 = this.getPath().getAt(i - 2);
var p2 = this.getPath().getAt(i - 1);
var m = (metres - olddist) / (dist - olddist);
return new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
}
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=geometry">
</script>

A-FRAME Extend component

Consider this:
<a-entity id="player">
<a-entity id="camera" camera look-controls></a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right"></a-entity>
</a-entity>
If I want to move my player through the scene, I would add wasd-controls to #player. Doing that, disregards the orientation of the head (camera): W always moves "north", wherever you are looking at. If I add wasd-controls to #camera, the head moves correctly but the controllers are left behind.
So I thought of creating a custom wasd-controls, but I am unable to extend that component. I have been successful copying and pasting all the code, but that is very nasty.
This did not work: AFrame extend component and override.
Any idea?
my-wasd-controls.js
var KEYCODE_TO_CODE = require('aframe/src/constants').keyboardevent.KEYCODE_TO_CODE;
var AFRAME = require('aframe');
var THREE = require('aframe/src/lib/three');
var utils = require('aframe/src/utils');
var bind = utils.bind;
var shouldCaptureKeyEvent = utils.shouldCaptureKeyEvent;
var CLAMP_VELOCITY = 0.00001;
var MAX_DELTA = 0.2;
var KEYS = [
'KeyW', 'KeyA', 'KeyS', 'KeyD',
'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown'
];
/**
* WASD component to control entities using WASD keys.
*/
module.exports.Component = AFRAME.registerComponent('my-wasd-controls', {
schema: {
acceleration: {default: 65},
adAxis: {default: 'x', oneOf: ['x', 'y', 'z']},
adEnabled: {default: true},
adInverted: {default: false},
easing: {default: 20},
enabled: {default: true},
fly: {default: false},
head: {type: 'selector'},
wsAxis: {default: 'z', oneOf: ['x', 'y', 'z']},
wsEnabled: {default: true},
wsInverted: {default: false}
},
init: function () {
// To keep track of the pressed keys.
this.keys = {};
this.position = {};
this.velocity = new THREE.Vector3();
// Bind methods and add event listeners.
this.onBlur = bind(this.onBlur, this);
this.onFocus = bind(this.onFocus, this);
this.onKeyDown = bind(this.onKeyDown, this);
this.onKeyUp = bind(this.onKeyUp, this);
this.onVisibilityChange = bind(this.onVisibilityChange, this);
this.attachVisibilityEventListeners();
},
tick: function (time, delta) {
var currentPosition;
var data = this.data;
var el = this.el;
var movementVector;
var position = this.position;
var velocity = this.velocity;
if (!velocity[data.adAxis] && !velocity[data.wsAxis] &&
isEmptyObject(this.keys)) { return; }
// Update velocity.
delta = delta / 1000;
this.updateVelocity(delta);
if (!velocity[data.adAxis] && !velocity[data.wsAxis]) { return; }
// Get movement vector and translate position.
currentPosition = el.getAttribute('position');
movementVector = this.getMovementVector(delta);
position.x = currentPosition.x + movementVector.x;
position.y = currentPosition.y + movementVector.y;
position.z = currentPosition.z + movementVector.z;
el.setAttribute('position', position);
},
remove: function () {
this.removeKeyEventListeners();
this.removeVisibilityEventListeners();
},
play: function () {
this.attachKeyEventListeners();
},
pause: function () {
this.keys = {};
this.removeKeyEventListeners();
},
updateVelocity: function (delta) {
var acceleration;
var adAxis;
var adSign;
var data = this.data;
var keys = this.keys;
var velocity = this.velocity;
var wsAxis;
var wsSign;
adAxis = data.adAxis;
wsAxis = data.wsAxis;
// If FPS too low, reset velocity.
if (delta > MAX_DELTA) {
velocity[adAxis] = 0;
velocity[wsAxis] = 0;
return;
}
// Decay velocity.
if (velocity[adAxis] !== 0) {
velocity[adAxis] -= velocity[adAxis] * data.easing * delta;
}
if (velocity[wsAxis] !== 0) {
velocity[wsAxis] -= velocity[wsAxis] * data.easing * delta;
}
// Clamp velocity easing.
if (Math.abs(velocity[adAxis]) < CLAMP_VELOCITY) { velocity[adAxis] = 0; }
if (Math.abs(velocity[wsAxis]) < CLAMP_VELOCITY) { velocity[wsAxis] = 0; }
if (!data.enabled) { return; }
// Update velocity using keys pressed.
acceleration = data.acceleration;
if (data.adEnabled) {
adSign = data.adInverted ? -1 : 1;
if (keys.KeyA || keys.ArrowLeft) { velocity[adAxis] -= adSign * acceleration * delta; }
if (keys.KeyD || keys.ArrowRight) { velocity[adAxis] += adSign * acceleration * delta; }
}
if (data.wsEnabled) {
wsSign = data.wsInverted ? -1 : 1;
if (keys.KeyW || keys.ArrowUp) { velocity[wsAxis] -= wsSign * acceleration * delta; }
if (keys.KeyS || keys.ArrowDown) { velocity[wsAxis] += wsSign * acceleration * delta; }
}
},
getMovementVector: (function () {
var directionVector = new THREE.Vector3(0, 0, 0);
var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ');
return function (delta) {
var rotation = (this.data.head || this.el).getAttribute('rotation');
var velocity = this.velocity;
var xRotation;
directionVector.copy(velocity);
directionVector.multiplyScalar(delta);
// Absolute.
if (!rotation) { return directionVector; }
xRotation = this.data.fly ? rotation.x : 0;
// Transform direction relative to heading.
rotationEuler.set(THREE.Math.degToRad(xRotation), THREE.Math.degToRad(rotation.y), 0);
directionVector.applyEuler(rotationEuler);
return directionVector;
};
})(),
attachVisibilityEventListeners: function () {
window.addEventListener('blur', this.onBlur);
window.addEventListener('focus', this.onFocus);
document.addEventListener('visibilitychange', this.onVisibilityChange);
},
removeVisibilityEventListeners: function () {
window.removeEventListener('blur', this.onBlur);
window.removeEventListener('focus', this.onFocus);
document.removeEventListener('visibilitychange', this.onVisibilityChange);
},
attachKeyEventListeners: function () {
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
},
removeKeyEventListeners: function () {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
},
onBlur: function () {
this.pause();
},
onFocus: function () {
this.play();
},
onVisibilityChange: function () {
if (document.hidden) {
this.onBlur();
} else {
this.onFocus();
}
},
onKeyDown: function (event) {
var code;
if (!shouldCaptureKeyEvent(event)) { return; }
code = event.code || KEYCODE_TO_CODE[event.keyCode];
if (KEYS.indexOf(code) !== -1) { this.keys[code] = true; }
},
onKeyUp: function (event) {
var code;
code = event.code || KEYCODE_TO_CODE[event.keyCode];
delete this.keys[code];
}
});
function isEmptyObject (keys) {
var key;
for (key in keys) { return false; }
return true;
}
I would still recommend copy and pasting it. If you ever upgrade A-Frame later, and wasd-controls changes, your extensions will probably break.
Changing the prototype should work (e.g., AFRAME.components['wasd-controls'].Component.prototype.foo = () => {}). The prototype methods are writable.
Another alternative is to overwrite the method on the component instance. el.components['wasd-controls'].foo = () => {}.

How to implement dynamic reflections in A-Frame

I have an A-Frame scene consisting of a horizontal plane, some lights and a vertical plane on which a video is playing. I am trying to achieve reflections on the horizontal plane such that the vertical plane above it is reflected. I intend to set reduced opacity on the reflective surface. How can one do this in A-Frame?
I write a mirror component, here is my code:
<script>
AFRAME.registerComponent('mirror', {
schema:{
renderothermirror:{default:true},
},
init: function () {
var scene = this.el.sceneEl;
scene.addEventListener('render-target-loaded',this.OnRenderLoaded.bind(this));
},
OnRenderLoaded: function()
{
var mirrorObj = this.el.getOrCreateObject3D('mesh',THREE.Mesh);
var cameraEl = document.querySelector('a-entity[camera]')
if(!cameraEl)
{
cameraEl = document.querySelector('a-camera');
}
var camera = cameraEl.components.camera.camera;
var scene = this.el.sceneEl;
this.renderer = scene.renderer;
this.mirror = new THREE.Mirror( this.renderer, camera, { clipBias: 0.003, textureWidth: window.innerWidth, textureHeight: window.innerHeight, color: 0x777777 } );
mirrorObj.material =this.mirror.material;
mirrorObj.add(this.mirror);
},
tick: function () {
if(!this.data.renderothermirror)
{
this.mirror.render();
}
else
{
var mirrors = [];
var mirrorEls = document.querySelectorAll("a-entity[mirror]");
for(var i=0;i<mirrorEls.length;i++)
{
if(mirrorEls[i]!=this.el)
{
mirrors.push(mirrorEls[i].components.mirror.mirror);
}
}
if(mirrors.length === 0)
{
this.mirror.render();
}
for(var i = 0; i<mirrors.length;i++)
{
this.mirror.renderWithMirror(mirrors[i]);
}
}
}
});
</script>
You also need to use Mirror.js, you can find it here:Mirror.js
Just attach the mirror component to your planes. e.g. <a-plane mirror>.

SimpleModal doesn't work in IE 9 (inside Iframe)

I'm getting following error when using IE 9 (Chrome and FireFox works great):
SCRIPT438: Object doesn't support property or method 'removeExpression'
jquery.simplemodal.1.4.2.min.js, line 16 character 133
Simple Modal is called inside Iframe. jQuery.min (1.7.1) is included before SimpleModal (1.4.2) in the Iframe.
The code responsible for showing modal dialog:
function OpenContextByClass(cssClass, posY, posX) {
var winHeight = $(window).height();
var winWidth = $(window).width();
$('.' + cssClass).modal({
overlayClose: true,
position: [posY, posX],
appendTo: 'form',
onOpen: function (dialog) { dialog.overlay.fadeIn('fast', function () { dialog.container.slideDown('fast', function () { dialog.data.fadeIn('fast'); }); }); },
onShow: function (d) {
var self = this;
self.container = d.container[0];
var title = $('.' + cssClass, self.container);
title.show();
$('.' + cssClass, self.container).show();
setTimeout(function () {
var currentPositionX = posX;
var currentPositionY = posY;
var currentWidth = $('.' + cssClass, self.container).width() + 50;
var currentHeight = $('.' + cssClass, self.container).height() + 50;
posY = (currentPositionY + currentHeight) < winHeight ? currentPositionY : (winHeight - currentHeight);
posX = (currentPositionX + currentWidth) < winWidth ? currentPositionX : (winWidth - currentWidth);
d.container.animate(
{ left: posX, top: posY },
500,
function () {
$('.' + cssClass, self.container).show();
}
);
}, 550);
}
});
}
I have got the same problem. And I found this article: http://help.dottoro.com/ljuvxilu.php
The support for dynamic properties has been removed in Internet Explorer 9, so none of the getExpression, removeExpression, setExpression and recalc methods are supported. These methods exist in version 8, but using them raises exceptions.

Google Map Tooltip

I wonder whether someone may be able to help me please.
I've been working on a tooltip for markers that I place on a google map. I can get this to work showing the information that I would like the user to see, in this case the fields name and address, so the code line is title: name+address.
Could someone please tell me how I could put a space between these so the tooltip would read 'name address' rather than 'nameaddress'.
I've tried all sorts of things using e.g.title: name'_'+ address, title: name' '+address and I can't get it to work.
Any help would be greatly appreciated.
Many thanks
Chris
You can try this
name + ' ' + address
NB: you need a space in the quotes and a + on either side.
I use this function to initialize started values:
//Inicialize map values
function initialize() {
latCenterMap=41.50347;
lonCenterMap=-5.74638;
zommCeneterMap=14;
latPoint=41.50347;
lonPoint=-5.74638;
//Values default initialize
var latlng = new google.maps.LatLng(latCenterMap, lonCenterMap);
var mapOptions = {
zoom: zommCeneterMap,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map-canvas_'), mapOptions);
codePoint(map, lat, lon);
}
I used this function to set values point position into map
//Get position by Latitude and Longitude
function codePoint(map, lat, lon) {
var latlng = new google.maps.LatLng(parseFloat(lat), parseFloat(lon));
var title = "Your text";
var iconPoint = new google.maps.MarkerImage('images/pointBlue.png',
//Measure image
new google.maps.Size(25,25),
new google.maps.Point(0,0),
//Half measure image
new google.maps.Point(12.5,12.5)
);
marker = new google.maps.Marker({
position: latlng,
map: map,
icon: iconPoint,
tooltip: title
});
customTooltip(marker);
}
I use this function to create a tooltip to point position
//TOOLTIP
function customTooltip(marker){
// Constructor function
function Tooltip(opts, marker) {
// Initialization
this.setValues(opts);
this.map_ = opts.map;
this.marker_ = marker;
var div = this.div_ = document.createElement("div");
// Class name of div element to style it via CSS
div.className = "tooltip";
this.markerDragging = false;
}
Tooltip.prototype = {
// Define draw method to keep OverlayView happy
draw: function() {},
visible_changed: function() {
var vis = this.get("visible");
this.div_.style.visibility = vis ? "visible" : "hidden";
},
getPos: function(e) {
var projection = this.getProjection();
// Position of mouse cursor
var pixel = projection.fromLatLngToDivPixel(e.latLng);
var div = this.div_;
// Adjust the tooltip's position
var gap = 15;
var posX = pixel.x + gap;
var posY = pixel.y + gap;
var menuwidth = div.offsetWidth;
// Right boundary of the map
var boundsNE = this.map_.getBounds().getNorthEast();
boundsNE.pixel = projection.fromLatLngToDivPixel(boundsNE);
if (menuwidth + posX > boundsNE.pixel.x) {
posX -= menuwidth + gap;
}
div.style.left = posX + "px";
div.style.top = posY + "px";
if (!this.markerDragging) {
this.set("visible", true);
}
},
// This is added to avoid using listener (Listener is not working when Map is quickly loaded with icons)
getPos2: function(latLng) {
var projection = this.getProjection();
// Position of mouse cursor
var pixel = projection.fromLatLngToDivPixel(latLng);
var div = this.div_;
// Adjust the tooltip's position
var gap = 5;
var posX = pixel.x + gap;
var posY = pixel.y + gap;
var menuwidth = div.offsetWidth;
// Right boundary of the map
var boundsNE = this.map_.getBounds().getNorthEast();
boundsNE.pixel = projection.fromLatLngToDivPixel(boundsNE);
if (menuwidth + posX > boundsNE.pixel.x) {
posX -= menuwidth + gap;
}
div.style.left = posX + "px";
div.style.top = posY + "px";
if (!this.markerDragging) {
this.set("visible", true);
}
},
addTip: function() {
var me = this;
var g = google.maps.event;
var div = me.div_;
div.innerHTML = me.get("text").toString();
// Tooltip is initially hidden
me.set("visible", false);
// Append the tooltip's div to the floatPane
me.getPanes().floatPane.appendChild(this.div_);
// In IE this listener gets randomly lost after it's been cleared once.
// So keep it out of the listeners array.
g.addListener(me.marker_, "dragend", function() {
me.markerDragging = false; });
// Register listeners
me.listeners = [
// g.addListener(me.marker_, "dragend", function() {
// me.markerDragging = false; }),
g.addListener(me.marker_, "position_changed", function() {
me.markerDragging = true;
me.set("visible", false); }),
g.addListener(me.map_, "mousemove", function(e) {
me.getPos(e); })
];
},
removeTip: function() {
// Clear the listeners to stop events when not needed.
if (this.listeners) {
for (var i = 0, listener; listener = this.listeners[i]; i++) {
google.maps.event.removeListener(listener);
}
delete this.listeners;
}
// Remove the tooltip from the map pane.
var parent = this.div_.parentNode;
if (parent) parent.removeChild(this.div_);
}
};
function inherit(addTo, getFrom) {
var from = getFrom.prototype; // prototype object to get methods from
var to = addTo.prototype; // prototype object to add methods to
for (var prop in from) {
if (typeof to[prop] == "undefined") to[prop] = from[prop];
}
}
// Inherits from OverlayView from the Google Maps API
inherit(Tooltip, google.maps.OverlayView);
var tooltip = new Tooltip({map: map}, marker);
tooltip.bindTo("text", marker, "tooltip");
google.maps.event.addListener(marker, 'mouseover', function() {
tooltip.addTip();
tooltip.getPos2(marker.getPosition());
});
google.maps.event.addListener(marker, 'mouseout', function() {
tooltip.removeTip();
});
}
I use this style to css file
//CSS
.tooltip {
position:absolute;
top:0;
left:0;
z-index: 300;
width: 11.5em;
padding: 5px;
font-size: 12pt;
font-family: klavika;
color: #fff;
background-color: #04A2CA;
border-radius: 10px;
box-shadow: 2px 2px 5px 0 rgba(50, 50, 50, 0.75);
}

Resources