I've tried different solutions to animate a sidenav bar rise from bottom right corner. This is the last solution, maybe the best one ( because i'm animating the transform scale() ) in the previous ( width / height ) but the animation still sluggish on the cut corner... with the cut line acting with strange squared forms during the animation ... so i'm looking to solve!
Here the scale() example on glitch
https://glitch.com/edit/#!/heady-grey-lantern
Even on w3schools site the simple animation of Side Navigation is little shimmy and flicker
https://www.w3schools.com/howto/howto_js_sidenav.asp
Here the core of animation
// css code
.sidenav {
width: 200vw;
height: 200vh;
width: 200dvw;
height: 200dvh;
position: fixed;
bottom: 0;
/* left: 0; */
right: 0;
/* cut corner */
--cutting: max(100vw, 100vh);
--cutting: max(150dvw, 150dvh);
clip-path: polygon(0 var(--cutting), var(--cutting) 0,100% 0,100% 100%,0 100%);
/* z-index: 1; */
background-color: #111;
/* overflow-x: hidden; */
will-change: transform;
transform: scale(0);
transform-origin: bottom right;
}
// Js code
const sidenav = document.getElementById("mySidenav")
const duration = 500
const openSidenav = [
{ transform: 'scaleX(0) scaleY(0)' },
{ transform: 'scaleX(1) scaleY(1)' }
]
const closeSidenav = [
{ transform: 'scaleX(1) scaleY(1)' },
{ transform: 'scaleX(0) scaleY(0)' }
]
const openNav = () => {
console.log('#NAV >> Open')
// document.getElementById("mySidenav").style.width = '175vw'
// document.getElementById("mySidenav").style.height = '175vh'
sidenav.animate(
openSidenav, {
duration,
iterations: 1,
easing: 'ease-in',
fill: 'forwards'
}
)
}
const closeNav = () => {
console.log('#NAV >> Close')
// document.getElementById("mySidenav").style.width = '0'
// document.getElementById("mySidenav").style.height = '0'
sidenav.animate(
closeSidenav, {
duration,
iterations: 1,
easing: 'ease-in',
fill: 'forwards'
}
)
}
Last, better one!
/* css */
#sidenav {
width: 100vw;
height: 100vh;
width: 100dvw;
height: 100dvh;
position: fixed;
/* cut corner */
--cutting: max(100vw, 100vh);
clip-path: polygon(0 var(--cutting), var(--cutting) 0,100% 0,100% 100%,0 100%);
/* set transition as in WebAnimation API */
transition: all 0.5s;
will-change: transform;
transform: scale(0);
transform-origin: bottom right;
z-index: 11;
background-color: var(--surface1);
display: grid;
justify-items: center;
align-items: center;
/* justify-items: end;
align-items: end; */
}
#sidenav[active] {
--cutting: 0;
transition: all 0.5s;
}
/* --------------- Mobile Nav -------------- */
nav {
/* TODO fallback inline-size: 75vw;
block-size: 75vh; */
inline-size: 75dvw;
block-size: 75dvh;
display: block;
flex-direction: column;
background-color: crimson;
}
// javascript animations
const openSidenav = [
{ transform: 'scale(0)' },
{ transform: 'scale(0.25)' },
{ transform: 'scale(0.5)' },
{ transform: 'scale(0.75)' },
{ transform: 'scale(1)' }
]
const closeSidenav = [
{ transform: 'scale(1)' },
{ transform: 'scale(0.75)' },
{ transform: 'scale(0.5)' },
{ transform: 'scale(0.25)' },
{ transform: 'scale(0)' }
]
const slideIn = [
{ transform: 'translateX(100%)' },
{ transform: 'translateX(0)', opacity: 1 }
]
const slideOut = [
{ transform: 'translateX(0)' },
{ transform: 'translateX(100%)', opacity: 0 }
]
// duration animation
const openSidenavDuration = 500
const closeSidenavDuration = 300
// duration animation per a tag
const slideInDuration = 200
const slideOutDuration = 100
// javascript
openNav (e) {
const sidenav =
this.renderRoot.getElementById('sidenav')
this.sidenavIsOpen = true
const openAnimation = sidenav.animate(
openSidenav, {
duration: openSidenavDuration,
iterations: 1,
easing: 'ease-in',
fill: 'forwards'
}
)
openAnimation.addEventListener('finish', (e) => {
// console.log('#EVENT >> ', e.type)
sidenav.setAttribute('active', '')
// sidenav.style.setProperty('--cutting', 0)
})
}
closeNav (e) {
const sidenav =
this.renderRoot.getElementById('sidenav')
this.sidenavIsOpen = false
sidenav.animate(
closeSidenav, {
duration: closeSidenavDuration,
iterations: 1,
easing: 'ease-in',
fill: 'forwards',
delay: slideOutDuration * 3
}
)
sidenav.removeAttribute('active')
}
When I clicked over cluster, the features not show correctly, I tried stopping the animation but it didn't work. Is another option to show animation for each feature?
// Cluster Source
clusterSource = new ol.source.Cluster({
distance: distanceFt,
source: new ol.source.Vector()
});
// Animated cluster layer
clusterLayer = new ol.layer.AnimatedCluster({
name: 'Cluster',
source: clusterSource,
animationDuration: 700, //$("#animatecluster").prop('checked') ? 700 : 0,
// Cluster style
style: getStyle
});
// Transparent style to handle click on animation
var transparent = [0, 0, 0, 0.01];
var filltransparent = [0, 0, 0, 0];
var transparentStyle =
[new ol.style.Style(
{
image: new ol.style.RegularShape({ radius: 10, radius2: 5, points: 5, fill: new ol.style.Fill({ color: transparent }) }),
stroke: new ol.style.Stroke({ color: transparent, width: 2 }),
fill: new ol.style.Fill({ color: filltransparent })
})
];
// animation
var animacion = new ol.featureAnimation.Zoom({
fade: ol.easing.easeOut,
duration: 2000,
easing: ol.easing["easeOut"],
repeat: 100,
hiddenStyle: transparentStyle
});
ft_animados[idx] = vector_anim.animateFeature(anim_ft[i], animacion(anim_ft[i]));
I have two function:
function ft_animacion(f) {
//var style = f.getStyle();
var efecto = new ol.style.Style({
image: new ol.style["Circle"]({
radius: 30,
points: 4,
stroke: new ol.style.Stroke({ color: 'red', width: 2 })
})
});
f.setStyle(efecto);
return f;
}
function addFeatures(ffs, centrar) {
// some code lines
for (var i = 0; i < ffs.length; i++) {
features[i] = new ol.Feature();
features[i].setProperties(ffs[i]);
var geometry = new ol.geom.Point(transform([parseFloat(ffs[i].lon), parseFloat(ffs[i].lat)]));
features[i].setGeometry(geometry);
if (ffs[i].content.ind) {
if (ffs[i].content.ind.includes("Pendiente")) {
anim_ft.push(ft_animacion(features[i]));
}
}
}
// some code lines
}
I resolve my problem using feature.clone().
So edit the line anim_ft.push(ft_animacion(features[i])) to anim_ft.push(ft_animacion(features[i].clone()))
I dont noticed that when I use the function ft_animacion I was overwriting the style of feature.
I use
https://openlayers.org/en/latest/examples/feature-move-animation.html
code snippet:
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
var route = /** #type {module:ol/geom/LineString~LineString} */ (new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
}));
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({ // CircleStyle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
var vectorLayer = new ol.layer.Vector({ // VectorLayer({
source: new ol.source.Vector({ // VectorSource({
features: [routeFeature, geoMarker, startMarker, endMarker]
}),
style: function(feature) {
// hide geoMarker if animation is active
if (animating && feature.get('type') === 'geoMarker') {
return null;
}
return styles[feature.get('type')];
}
});
var center = [-5639523.95, -3501274.52];
var map = new ol.Map({
target: document.getElementById('map'),
loadTilesWhileAnimating: true,
view: new ol.View({
center: center,
zoom: 9,
minZoom: 2,
maxZoom: 19
}),
layers: [
new ol.layer.Tile({ // TileLayer({
source: new ol.source.OSM()
}),
vectorLayer
]
});
var moveFeature = function(event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating) {
var elapsedTime = frameState.time - now;
// here the trick to increase speed is to jump some indexes
// on lineString coordinates
var index = Math.round(speed * elapsedTime / 1000);
if (index >= routeLength) {
stopAnimation(true);
return;
}
var currentPoint = new ol.geom.Point(routeCoords[index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature, styles.geoMarker);
}
// tell OpenLayers to continue the postcompose animation
map.render();
};
function startAnimation() {
if (animating) {
stopAnimation(false);
} else {
animating = true;
now = new Date().getTime();
speed = speedInput.value;
startButton.textContent = 'Cancel Animation';
// hide geoMarker
geoMarker.setStyle(null);
// just in case you pan somewhere else
map.getView().setCenter(center);
map.on('postcompose', moveFeature);
map.render();
}
}
/**
* #param {boolean} ended end of animation.
*/
function stopAnimation(ended) {
animating = false;
startButton.textContent = 'Start Animation';
// if animation cancelled set the marker at the beginning
var coord = ended ? routeCoords[routeLength - 1] : routeCoords[0];
/** #type {module:ol/geom/Point~Point} */
(geoMarker.getGeometry())
.setCoordinates(coord);
//remove listener
map.un('postcompose', moveFeature);
}
startButton.addEventListener('click', startAnimation, false);
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.1.3/build/ol.js"></script>
<div id="map" class="map"></div>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="999" step="10" value="60">
</label>
<button id="start-animation">Start Animation</button>
but I need to move marker smoothly between two point (or many)
See ol-ext : featureAnimation
example from that library's documentation
code snippet (example from the OpenLayers documentation using ol-ext):
// use window.onload so can move encoded polyline definition to bottom
window.onload = function() {
var route = /** #type {module:ol/geom/LineString~LineString} */ (new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
}));
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routePts = new ol.Feature({
type: 'route',
geometry: new ol.geom.MultiPoint(routeCoords)
});
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
}),
image: new ol.style.Circle({
radius: 1,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'red'
})
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({ // CircleStyle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
var vectorLayer = new ol.layer.Vector({ // VectorLayer({
source: new ol.source.Vector({ // VectorSource({
features: [routePts, routeFeature, geoMarker, startMarker, endMarker]
}),
style: function(feature) {
return styles[feature.get('type')];
}
});
var center = [-5639523.95, -3501274.52];
var map = new ol.Map({
target: document.getElementById('map'),
loadTilesWhileAnimating: true,
view: new ol.View({
center: center,
zoom: 9,
minZoom: 2,
maxZoom: 19
}),
layers: [
new ol.layer.Tile({ // TileLayer({
source: new ol.source.OSM()
}),
vectorLayer
]
});
console.log("init speed=" + speedInput.value / 100);
function startAnimation() {
if (animating) animating.stop();
console.log("update speed=" + Number(speedInput.value) / 100);
// anim.set("speed", Number(speedInput.value)/10);
var anim = new ol.featureAnimation.Path({
path: route,
easing: ol.easing.linear,
speed: Number(speedInput.value) / 100
});
animating = vectorLayer.animateFeature(geoMarker, anim);
}
geoMarker.on('change', function() {
map.getView().setCenter(geoMarker.getGeometry().getCoordinates());
});
startButton.addEventListener('click', startAnimation, false);
}
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.1.3/build/ol.js"></script>
<script src="https://cdn.rawgit.com/Viglino/ol-ext/master/dist/ol-ext.js"></script>
<button id="start-animation">Start Animation</button>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="4999" step="10" value="500">
</label>
<div id="map" class="map"></div>
The OpenLayers example doesn't move smoothly as the only coordinates it uses are the points where the linestring changes direction. Therefore long straights are traversed much faster than the curved sections. The ol-ext method breaks the linestring into small equal length segments, but it seems to be animating the view to keep up with the marker instead of simply animating the marker. By reusing the splitLineString function from the second part of the answer to this question https://gis.stackexchange.com/questions/306976/add-image-along-the-linestring to finely divide the linestring and setting only one icon style based on a timer based index it is possible to achieve a smooth movement independently of the view (best seen in a full screen window) and also allows the icon to stay aligned with the direction of travel.
// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
'hldhx#lnau`BCG_EaC??cFjAwDjF??uBlKMd#}#z#??aC^yk#z_#se#b[wFdE??wFfE}N',
'fIoGxB_I\\gG}#eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
'aMyBiHOkFNoI`CcVvM??gG^gF_#iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
'kUFmq#hBiOqBgTwS??iYse#gYq\\cp#ce#{vA}s#csJqaE}{#iRaqE{lBeRoIwd#_T{]_',
'Ngn#{PmhEwaA{SeF_u#kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs#',
'sPua#_OkXaMeT_Nwk#ob#gV}TiYs[uTwXoNmT{Uyb#wNg]{Nqa#oDgNeJu_#_G}YsFw]k',
'DuZyDmm#i_#uyIJe~#jCg|#nGiv#zUi_BfNqaAvIow#dEed#dCcf#r#qz#Egs#{Acu#mC',
'um#yIey#gGig#cK_m#aSku#qRil#we#{mAeTej#}Tkz#cLgr#aHko#qOmcEaJw~C{w#ka',
'i#qBchBq#kmBS{kDnBscBnFu_Dbc#_~QHeU`IuyDrC_}#bByp#fCyoA?qMbD}{AIkeAgB',
'k_A_A{UsDke#gFej#qH{o#qGgb#qH{`#mMgm#uQus#kL{_#yOmd#ymBgwE}x#ouBwtA__',
'DuhEgaKuWct#gp#cnBii#mlBa_#}|Asj#qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
'Xyn#ywMyOyqD{_#cfIcDe}#y#aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
'itAhT}x#bE}Z_#qW_Kwv#qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi#',
'xBycBeCauBoF}}#qJioAww#gjHaPopA_NurAyJku#uGmi#cDs[eRaiBkQstAsQkcByNma',
'CsK_uBcJgbEw#gkB_#ypEqDoqSm#eZcDwjBoGw`BoMegBaU_`Ce_#_uBqb#ytBwkFqiT_',
'fAqfEwe#mfCka#_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
'~CfIewG|YibQxBssB?es#qGciA}RorAoVajA_nAodD{[y`AgPqp#mKwr#ms#umEaW{dAm',
'b#umAw|#ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux#}_Dui#_eB_u#guCuyAuiHukA_',
'lKszAu|OmaA{wKm}#clHs_A_rEahCssKo\\sgBsSglAqk#yvDcS_wAyTwpBmPc|BwZknF',
'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq#m~Hym#c`EuiBudIabB{hF{pWifx#snA',
'w`GkFyVqf#y~BkoAi}Lel#wtc#}`#oaXi_C}pZsi#eqGsSuqJ|Lqeb#e]kgPcaAu}SkDw',
'zGhn#gjYh\\qlNZovJieBqja#ed#siO{[ol\\kCmjMe\\isHorCmec#uLebB}EqiBaCg}',
'#m#qwHrT_vFps#kkI`uAszIrpHuzYxx#e{Crw#kpDhN{wBtQarDy#knFgP_yCu\\wyCwy',
'A{kHo~#omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm#_bAumFo}DgqA_uByi',
'#swC~AkzDlhA}xEvcBa}Cxk#ql#`rAo|#~bBq{#``Bye#djDww#z_C_cAtn#ye#nfC_eC',
'|gGahH~s#w}#``Fi~FpnAooC|u#wlEaEedRlYkrPvKerBfYs}Arg#m}AtrCkzElw#gjBb',
'h#woBhR{gCwGkgCc[wtCuOapAcFoh#uBy[yBgr#c#iq#o#wvEv#sp#`FajBfCaq#fIipA',
'dy#ewJlUc`ExGuaBdEmbBpBssArAuqBBg}#s#g{AkB{bBif#_bYmC}r#kDgm#sPq_BuJ_',
's#{X_{AsK_d#eM{d#wVgx#oWcu#??aDmOkNia#wFoSmDyMyCkPiBePwAob#XcQ|#oNdCo',
'SfFwXhEmOnLi\\lbAulB`X_d#|k#au#bc#oc#bqC}{BhwDgcD`l#ed#??bL{G|a#eTje#',
'oS~]cLr~Bgh#|b#}Jv}EieAlv#sPluD{z#nzA_]`|KchCtd#sPvb#wSb{#ko#f`RooQ~e',
'[upZbuIolI|gFafFzu#iq#nMmJ|OeJn^{Qjh#yQhc#uJ~j#iGdd#kAp~BkBxO{#|QsAfY',
'gEtYiGd]}Jpd#wRhVoNzNeK`j#ce#vgK}cJnSoSzQkVvUm^rSgc#`Uql#xIq\\vIgg#~k',
'Dyq[nIir#jNoq#xNwc#fYik#tk#su#neB}uBhqEesFjoGeyHtCoD|D}Ed|#ctAbIuOzqB',
'_}D~NgY`\\um#v[gm#v{Cw`G`w#o{AdjAwzBh{C}`Gpp#ypAxn#}mAfz#{bBbNia#??jI',
'ab#`CuOlC}YnAcV`#_^m#aeB}#yk#YuTuBg^uCkZiGk\\yGeY}Lu_#oOsZiTe[uWi[sl#',
'mo#soAauAsrBgzBqgAglAyd#ig#asAcyAklA}qAwHkGi{#s~#goAmsAyDeEirB_{B}IsJ',
'uEeFymAssAkdAmhAyTcVkFeEoKiH}l#kp#wg#sj#ku#ey#uh#kj#}EsFmG}Jk^_r#_f#m',
'~#ym#yjA??a#cFd#kBrCgDbAUnAcBhAyAdk#et#??kF}D??OL'
].join('');
function splitLineString(geometry, minSegmentLength, options) {
function calculatePointsDistance(coord1, coord2) {
var dx = coord1[0] - coord2[0];
var dy = coord1[1] - coord2[1];
return Math.sqrt(dx * dx + dy * dy);
};
function calculateSplitPointCoords(startNode, nextNode, distanceBetweenNodes, distanceToSplitPoint) {
var d = distanceToSplitPoint / distanceBetweenNodes;
var x = nextNode[0] + (startNode[0] - nextNode[0]) * d;
var y = nextNode[1] + (startNode[1] - nextNode[1]) * d;
return [x, y];
};
function calculateAngle(startNode, nextNode, alwaysUp) {
var x = (startNode[0] - nextNode[0]);
var y = (startNode[1] - nextNode[1]);
var angle = Math.atan(x/y);
if (!alwaysUp) {
angle = y > 0 ? angle + Math.PI : x < 0 ? angle + Math.PI*2 : angle;
}
return angle;
};
var splitPoints = [];
var coords = geometry.getCoordinates();
var coordIndex = 0;
var startPoint = coords[coordIndex];
var nextPoint = coords[coordIndex + 1];
var angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
var n = Math.ceil(geometry.getLength()/minSegmentLength);
var segmentLength = geometry.getLength() / n;
var currentSegmentLength = options.midPoints ? segmentLength/2 : segmentLength;
for (var i = 0; i <= n; i++) {
var distanceBetweenPoints = calculatePointsDistance(startPoint, nextPoint);
currentSegmentLength += distanceBetweenPoints;
if (currentSegmentLength < segmentLength) {
coordIndex++;
if(coordIndex < coords.length - 1) {
startPoint = coords[coordIndex];
nextPoint = coords[coordIndex + 1];
angle = calculateAngle(startPoint, nextPoint, options.alwaysUp);
i--;
continue;
} else {
if (!options.midPoints) {
var splitPointCoords = nextPoint;
splitPointCoords.push(angle);
splitPoints.push(splitPointCoords);
}
break;
}
} else {
var distanceToSplitPoint = currentSegmentLength - segmentLength;
var splitPointCoords = calculateSplitPointCoords(startPoint, nextPoint, distanceBetweenPoints, distanceToSplitPoint);
startPoint = splitPointCoords.slice();
splitPointCoords.push(angle);
splitPoints.push(splitPointCoords);
currentSegmentLength = 0;
}
}
return splitPoints;
};
var route = new ol.format.Polyline({
factor: 1e6
}).readGeometry(polyline, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;
var routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
var startMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[0])
});
var endMarker = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords[routeLength - 1])
});
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var repeat = false;
var timer = -1;
var style = function(feature, resolution) {
if (feature.get('type') == 'route') {
var styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8],
})
})
];
if (timer < 0) {
feature.unset('splitPoints', true);
} else {
var splitPoints = feature.get('splitPoints');
if (!splitPoints) {
splitPoints = splitLineString(feature.getGeometry(), 2 * resolution, {alwaysUp: false, midPoints: true});
feature.set('splitPoints', splitPoints, true);
}
if (!repeat && timer >= splitPoints.length) {
stopAnimation(true);
} else {
var index = timer % splitPoints.length;
var point = splitPoints[index];
styles.push(
new ol.style.Style({
geometry: new ol.geom.Point([point[0],point[1]]),
image: new ol.style.Icon({
src: 'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png',
rotation: point[2]
}),
zIndex: 1
})
);
}
}
return styles;
} else {
return [
new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v4.6.5/examples/data/icon.png',
})
})
];
}
}
var vector = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature, startMarker, endMarker]
}),
style: style
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-5639523.95, -3501274.52],
zoom: 10
})
});
var animating = false;
var token;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
function startAnimation() {
if (animating) {
stopAnimation(false);
} else {
animating = true;
startButton.textContent = 'Cancel Animation';
token = setInterval( function(){
timer++;
vector.setStyle(style);
}, 6000/speedInput.value);
}
}
function stopAnimation(ended) {
animating = false;
startButton.textContent = 'Start Animation';
clearInterval(token);
timer = -1;
if (!ended) {
vector.setStyle(style);
}
}
startButton.addEventListener('click', startAnimation, false);
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
}
.map {
height: 90%;
width: 100%;
}
<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="999" step="10" value="200">
</label>
<button id="start-animation">Start Animation</button>
Does anybody know how can I change axis range in plotly's scatter3D via button click. I use relayout but it doesn't work
function updatet() {
Plotly.relayout(‘myDiv’, ‘zaxis.range’, [[0, 100]]);
}
Nothing happens (my range is still [0,1800])
For 3D plots zaxis.range needs to be inside scene in order to work.
Note: Your ticks look like some copy&paste/autoformat problem.
Plotly.d3.csv('https://raw.githubusercontent.com/plotly/datasets/master/3d-scatter.csv', function(err, rows) {
function unpack(rows, key) {
return rows.map(function(row) {
return row[key];
});
}
var trace1 = {
x: unpack(rows, 'x1'),
y: unpack(rows, 'y1'),
z: unpack(rows, 'z1'),
mode: 'markers',
marker: {
size: 12,
line: {
color: 'rgba(217, 217, 217, 0.14)',
width: 0.5
},
opacity: 0.8
},
type: 'scatter3d'
};
var trace2 = {
x: unpack(rows, 'x2'),
y: unpack(rows, 'y2'),
z: unpack(rows, 'z2'),
mode: 'markers',
marker: {
color: 'rgb(127, 127, 127)',
size: 12,
symbol: 'circle',
line: {
color: 'rgb(204, 204, 204)',
width: 1
},
opacity: 0.9
},
type: 'scatter3d'
};
var data = [trace1, trace2];
var layout = {
margin: {
l: 0,
r: 0,
b: 0,
t: 0,
}
};
Plotly.newPlot('myDiv', data, layout);
});
document.getElementById('updateButton').addEventListener('click', function() {
Plotly.relayout('myDiv', 'scene.zaxis.range', [0, 10 * Math.random()]);
});
<script src="https://cdn.plot.ly/plotly-latest.min.js">
</script>
<div id="myDiv" style="width:100%;height:100%"></div>
<button id='updateButton'>Change axis
</button>
I have a vertical and a horizontal lines and a circle on my stage, trying to keep the circle centered on the corssoing of the two lines when I move either line, here is my script that does not work:
var cy = 512;
var cx = 512;
var gy = 0;
var gx = 0;
var stage1 = new Kinetic.Stage({
container: 'container',
width: 1024,
height: 1024
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Layer();
var circle1 = new Kinetic.Circle({
x: cx + gx,
y: cy + gy,
radius: 140,
stroke: '#00ffff',
strokeWidth: 4,
opacity: 0.5
});
circle.add(circle1);
var GreenLine1 = new Kinetic.Line({
points: [0, 512, 1024, 512],
stroke: 'green',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round',
opacity: 0.3
});
var BlueLine1 = new Kinetic.Line({
points: [512, 0, 512, 1024],
stroke: '#0080c0',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round',
opacity: 0.5
});
var bgroup1 = new Kinetic.Group({
draggable: true,
dragBoundFunc: function (pos) {
return {
x: pos.x,
y: this.getAbsolutePosition().y
}
}
});
var ggroup1 = new Kinetic.Group({
draggable: true,
dragBoundFunc: function (pos) {
return {
x: this.getAbsolutePosition().x,
y: pos.y
}
}
});
bgroup1.add(BlueLine1);
ggroup1.add(GreenLine1);
layer.add(bgroup1);
layer.add(ggroup1);
stage1.add(circle);
stage1.add(layer);
BlueLine1.on('mouseover', function () {
document.body.style.cursor = 'e-resize';
});
BlueLine1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
GreenLine1.on('mouseover', function () {
document.body.style.cursor = 'n-resize';
});
GreenLine1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
ggroup1.on('dragend', function (event) {
var gy = ggroup1.getPosition().y;
circle.draw();
});
ggroup1.on('dragstart', function (event) {
circle1.moveTo(ggroup1);
});
bgroup1.on('dragstart', function (event) {
circle1.moveTo(bgroup1);
});
bgroup1.on('dragend', function (event) {
var gx = bgroup1.getPosition().x;
circle.draw();
});
I would appreciate your suggetions, thanks in advance
Keeping your circle in your crosshairs
May I suggest a simpler version of your code?
Instead of maintaining 2 groups and moving the circle between the 2 groups, how about just coding the circle to automatically redraw itself at the intersection of the 2 lines.
So when the user moves your GreenLine1 or BlueLine1, just move your circle1 to the intersection of your “crosshairs”.
First, add a custom drawFunc to your circle1 that causes it to always draw in the crosshairs:
drawFunc: function(canvas) {
var context = canvas.getContext();
var centerX=BlueLine1.getPosition().x;
var centerY=GreenLine1.getPosition().y;
context.beginPath();
context.arc(centerX, centerY, this.getRadius(), 0, 2 * Math.PI, false);
context.lineWidth = this.getStrokeWidth();
context.strokeStyle = this.getStroke();
context.stroke();
},
Then, whenever the user drags either line, just trigger circle1 to redraw itself:
// keep circle in center of crosshairs
stage1.getDragLayer().afterDraw(function() {
layer.draw();
});
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/cgF8y/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.3-beta.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
}
</style>
<script>
$(function(){
function init(){
var cy = 512/2;
var cx = 512/2;
var gy = 0;
var gx = 0;
var stage1 = new Kinetic.Stage({
container: 'container',
width: 1024/2,
height: 1024/2
});
var layer = new Kinetic.Layer();
stage1.add(layer);
var circle1 = new Kinetic.Circle({
drawFunc: function(canvas) {
var context = canvas.getContext();
var centerX=BlueLine1.getPosition().x;
var centerY=GreenLine1.getPosition().y;
context.beginPath();
context.arc(centerX, centerY, this.getRadius(), 0, 2 * Math.PI, false);
context.lineWidth = this.getStrokeWidth();
context.strokeStyle = this.getStroke();
context.stroke();
},
x: cx + gx,
y: cy + gy,
radius: 140/2,
stroke: '#00ffff',
strokeWidth: 4,
opacity: 0.5
});
layer.add(circle1);
var GreenLine1 = new Kinetic.Line({
points: [0, 512/2, 1024/2, 512/2],
stroke: 'green',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round',
opacity: 0.3,
draggable:true
});
layer.add(GreenLine1);
var BlueLine1 = new Kinetic.Line({
points: [512/2, 0, 512/2, 1024/2],
stroke: '#0080c0',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round',
opacity: 0.5,
draggable:true
});
layer.add(BlueLine1);
// keep circle in center of crosshairs
stage1.getDragLayer().afterDraw(function() {
layer.draw();
});
BlueLine1.on('mouseover', function () {
document.body.style.cursor = 'e-resize';
});
BlueLine1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
GreenLine1.on('mouseover', function () {
document.body.style.cursor = 'n-resize';
});
GreenLine1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
layer.draw();
} // end init();
init();
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>