I'm trying to make element return to its initial position once PanGestureHandler is released.
onGestureEvent = event(
[
{
nativeEvent: ({ translationY, state, velocityY }) =>
cond(
eq(state, State.ACTIVE),
// ignore this part, it's working fine
[
cond(
lessOrEq(this.scrollOffset, 0),
set(
this.gestureY,
divide(sub(translationY, this.ignoredGestureY), 2)
),
set(this.ignoredGestureY, translations)
)
],
[
// here I invoke bounce back animation
set(
this.gestureY,
runTiming(this.backClock, this.gestureY, new Value(0))
)
]
)
}
],
{ useNativeDriver: true }
)
Here is runTiming function:
function runTiming (clock, value, dest, startStopClock = true) {
const state = {
finished: new Value(0),
position: new Value(0),
frameTime: new Value(3000),
time: new Value(3000)
}
const config = {
toValue: new Value(0),
duration: 3000,
easing: Easing.ease
}
return [
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.frameTime, 3000),
set(state.time, 3000),
set(state.position, value),
set(config.toValue, dest),
startClock(clock)
]),
timing(clock, state, config),
cond(state.finished, stopClock(clock)),
state.position
]
}
The problem is that the element returns back instantly. Why is 3000 ms duration is not working?
Related
Using react-native-reanimated, I'm trying to repeat infinitely an animation which is also a repeated animation, with a delay.
With my code, the delay and the nested repeat animation are triggered but not the inifinite one.
Any ideas ?
useEffect(() => {
progress.value = withRepeat(
withDelay(2000, withRepeat(withTiming(0, { duration: 500 }), 6, true)),
-1,
true
);
}, []);
Like #Abe pointed out in his answer, reverse property of withRepeat is not supported when wrapped with other animation modifiers.
But, you can do this without setInterval - making use of withSequence to simulate the `reverse animation
// starting delay of 2000ms
// play animation 6 times
// repeat
progress.value =
withRepeat(
withDelay(2000, withRepeat(
withSequence(
// split duration of 500ms to 250ms
withTiming(goToValue, {duration: 250, easing: Easing.inOut(Easing.ease)}),
withTiming(initialValue, {duration: 250, easing: Easing.inOut(Easing.ease)}),
)
, 6))
, -1)
You've set the outer withRepeat to 1, so it should only repeat once, is that intended? Use a negative number to repeat indefinitely.
The docs for withRepeat say that withRepeat with the third argument (reverse) set to true doesn't work properly for the withDelay and withSequence animations, that could also be causing an issue. You might want to try reversing the animations manually in a withSequence call and repeating that.
No solution found with Reanimated, but as suggested by #Abe,a simple setInterval does the trick
useEffect(() => {
progress.value = withRepeat(withTiming(0, { duration: 400 }), 6, true);
const interval = setInterval(() => {
progress.value = withRepeat(withTiming(0, { duration: 400 }), 6, true);
}, 6000);
return () => clearInterval(interval);
}, []);
You can achieve that without setInterval, put withDelay on each animation.
withRepeat(
withSequence(
withDelay(
2000,
withTiming(0, {
duration: 300,
easing: Easing.inOut(Easing.ease),
}),
),
withDelay(
2000,
withTiming(1, {
duration: 300,
easing: Easing.inOut(Easing.ease),
}),
),
),
-1,
);
You can achieve this by using Animated.sequence
This code basically works by re-run the function when the animation done
function fadeAnimation() {
Animated.sequence([
Animated.timing(progress.value, {
toValue: 0,
duration: 500,
delay: 1000,
useNativeDriver: true,
}),
Animated.timing(progress.value, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
]).start(() => {
fadeAnimation();
});
}
useEffect(() => {
fadeAnimation()
}, []);
The deck.gl MVTLayer inherits from Layer that has onHover enabled which together with pickable should give interactivity. I am trying to get interactivity to work so I can do a popup with the data I hover. But in below code, I can get the onClick event to fire, but not the onHover event. what am I doing wrong
Thanks :)
import React from "react";
import DeckGL from "#deck.gl/react";
import { MVTLayer } from "#deck.gl/geo-layers";
const INITIAL_VIEW_STATE = {
longitude: -122.41669,
latitude: 37.7853,
zoom: 13,
pitch: 0,
bearing: 0
};
const layer = new MVTLayer({
id: "MVTLayer",
data: [
"https://tiles-a.basemaps.cartocdn.com/vectortiles/carto.streets/v1/{z}/{x}/{y}.mvt"
],
stroked: false,
getLineColor: [255, 0, 0],
getFillColor: (f) => {
switch (f.properties.layerName) {
case "poi":
return [0, 0, 0];
case "water":
return [120, 150, 180];
case "building":
return [255, 0, 0];
default:
return [240, 240, 240];
}
},
getPointRadius: 2,
pointRadiusUnits: "pixels",
getLineWidth: (f) => {
switch (f.properties.class) {
case "street":
return 6;
case "motorway":
return 10;
default:
return 1;
}
},
maxZoom: 14,
minZoom: 0,
onHover: (info) => console.log("Hover", info.object),
onClick: (info) => console.log("Click", info.object),
pickable: true
});
export default function App() {
return (
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
layers={[layer]}
></DeckGL>
);
}
It works .... I think I had a dodgy chrome instance running
I'm quite proud of what I've done: I have a menu which comprises 4 shapes. When you hover a shape, it's chaging color, growing and pushing the other shapes on top, while the rotation gets slower.
I read the ThreeJS docs and follow the advices of StackOverflow members.
I'm struggling with mouse interactions and window resizing: when I first open the browser, the mouseover doesn't seem to be called exactly when the mouse is over.
And when I resize the window, it's clearly messed up.
If anybody has a clue on what I'm doing wrong, thanks in advance :)
Here is my component:
<template>
<v-container>
<div #click="onClick" #mousemove="onMouseMove" id="menu3D" style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;"></div>
<v-row class="text-center">
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Accueil
</h2>
<v-row justify="center">
<p>
THIS IS ONLY A TEST
</p>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
import * as Three from 'three'
export default {
name: 'Home',
mounted() {
this.init();
},
methods: {
init: function() {
this.createScene();
this.createCamera();
this.userData.formes.forEach(x=>this.createShape(x))
this.addSpotlight(16777215/*'#fdffab'*/);
this.addAmbientLight();
this.animate();
window.addEventListener('resize', this.onResize())
},
onResize: function() {
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.camera.aspect = container.clientWidth / container.clientHeight;
this.camera.updateProjectionMatrix();
},
createScene: function() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true
});
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0xffffff,0);
container.appendChild(this.renderer.domElement);
},
createCamera: function() {
//let container = document.getElementById('container');
this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);
this.camera.position.set(0, 5, 20);
this.camera.zoom = 1;
},
createShape: function(shape) {
let material = new Three.MeshStandardMaterial({
"color": '#0000ff'/*16777215*/,
"roughness": 1,
"metalness": 0.5,
"emissive": 0,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"stencilWrite": false,
"stencilWriteMask": 255,
"stencilFunc": 519,
"stencilRef": 0,
"stencilFuncMask": 255,
"stencilFail": 7680,
"stencilZFail": 7680,
"stencilZPass": 7680
})
switch (shape.nom) {
case "Box": {
this.geometry = new Three.BoxBufferGeometry(1.8,1.8,1.8)
break;
}
case "Sphere": {
this.geometry = new Three.SphereBufferGeometry(1,8,6,0,6.283185,0, 3.141593)
break;
}
case "Dodecahedron": {
this.geometry = new Three.DodecahedronBufferGeometry(1.2,0)
break;
}
case "Icosahedron": {
this.geometry = new Three.IcosahedronBufferGeometry(1.5,0)
break;
}
}
this.mesh = new Three.Mesh(this.geometry, material)
this.mesh.name = shape.nom
this.mesh.userData = shape.userData
this.mesh.receiveShadow = true
this.mesh.castShadow = true
this.mesh.position.set(0, shape.userData.position.y, 0)
this.scene.add(this.mesh)
},
addSpotlight: function(color) {
const light = new Three.SpotLight(color, 2, 1000)
light.position.set(0, 0, 30)
this.scene.add(light)
},
addAmbientLight: function() {
const light = new Three.AmbientLight('#fff', 0.5)
this.scene.add(light)
},
verifForme: function(e) {
let t = this
let elt = t.scene.getObjectByName(e);
t.intersects = t.raycaster.intersectObject(elt);
if (t.intersects.length !== 0) {
// if it's not in the array, we put it at the beginning
if (t.userData.souris.indexOf(e)<0) {
t.userData.souris.unshift(e);
console.log(t.userData.souris[0] + " survolé!");
}
if (t.userData.souris[0] == e) {
let obj = t.intersects[0].object;
obj.material.color.set('#'+elt.userData.couleurs[1]);
obj.scale.set(obj.scale.x<1.4?obj.scale.x+t.VITESSE_ZOOM:obj.scale.x,obj.scale.y<1.4?obj.scale.y+t.VITESSE_ZOOM:obj.scale.y,obj.scale.z<1.4?obj.scale.z+t.VITESSE_ZOOM:obj.scale.z);
obj.rotation.y += t.VITESSE_ROTATION/t.RALENTISSEMENT
t.replacer(obj,obj.userData.position.y+obj.userData.decalage)
}
else {
t.retrecir(e,elt);
}
}
else {
if (t.userData.souris.indexOf(e)>=0) {
t.userData.souris = t.userData.souris.filter(forme => forme != e);
}
t.retrecir(e,elt);
}
},
onClick: function ( event ) {
event.preventDefault();
if (this.userData.souris.length >0 ) { console.log(this.userData.souris[0] + " clicked!"); }
else {
console.log("click outside!")
}
},
onMouseMove: function(event){
let container = document.getElementById('menu3D');
this.mouse.x = ( event.clientX / container.clientWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / container.clientHeight ) * 2 + 1;
//console.log(JSON.stringify(this.mouse))
},
replacer: function(e,py) {
// next line to prevent shaking
if (Math.abs(e.position.y - py) < 0.05) { return true }
let rhesus = 10*this.VITESSE_ZOOM
if (this.userData.souris[0] != e.name) { rhesus *= 3 }
//console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
if (e.position.y > py) { rhesus = -1 }
e.position.set(0,Math.trunc(10*e.position.y+rhesus)/10,0)
},
retrecir: function (n,e) {
// checking if the clicked element is on top
let dec = 0
let elt = this
if ((elt.userData.souris.length > 0) && (elt.userData.formes.map(x=>x.nom).indexOf(n)<elt.userData.formes.map(x=>x.nom).indexOf(elt.userData.souris[0]))) {
dec = Math.trunc(10*e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage*2.1)/10;
}
e.material.color.set('#'+e.userData.couleurs[0]);
e.rotation.y += elt.VITESSE_ROTATION
e.scale.set(e.scale.x>1?e.scale.x-elt.VITESSE_ZOOM:e.scale.x,e.scale.y>1?e.scale.y-elt.VITESSE_ZOOM:e.scale.y,e.scale.z>1?e.scale.z-elt.VITESSE_ZOOM:e.scale.z);
let newY = e.userData.position.y+dec
if (e.position.y != newY) {
elt.replacer(e,newY)
}
},
animate: function() {
let elt = this
requestAnimationFrame(this.animate);
this.raycaster.setFromCamera(this.mouse, this.camera);
this.userData.formes.map(x=>x.nom).forEach(x=>elt.verifForme(x))
if (this.userData.souris.length >0 ) { document.body.style.cursor = "pointer"; }
else { document.body.style.cursor = "default"; }
this.camera.updateProjectionMatrix();
this.renderer.render(this.scene, this.camera);
}
},
data: () => ({
scene: new Three.Scene(),
camera: null,
renderer: Three.WebGLRenderer,
mesh: new Three.Mesh,
factor:0,
mouse : new Three.Vector2(1, 1),
raycaster : new Three.Raycaster(),
intersects : [],
VITESSE_ROTATION: 0.05,
VITESSE_ZOOM: 0.1,
RALENTISSEMENT: 3,
userData: {
"souris": [],
"formes": [
{
"nom": "Box",
"userData": {
"position": {
"x": 0,
"y": 7.8,
"z": 0
},
"couleurs": [
"aaaaaa",
"095256"
],
"decalage": 0.5
}
},
{
"nom": "Icosahedron",
"userData": {
"position": {
"x": 0,
"y": 5.5,
"z": 0
},
"couleurs": [
"aaaaaa",
"087F8C"
],
"decalage": 0.5
}
},
{
"nom": "Dodecahedron",
"userData": {
"position": {
"x": 0,
"y": 3.1,
"z": 0
},
"couleurs": [
"aaaaaa",
"5AAA95"
],
"decalage": 0.4
}
},
{
"nom": "Sphere",
"userData": {
"position": {
"x": 0,
"y": 1,
"z": 0
},
"couleurs": [
"aaaaaa",
"86A873"
],
"decalage": 0.2
}
}
]
}
}),
}
</script>
I investigated the problem using the code you provided and fixed it locally so I'm hoping it also works for you. The issues are the following:
A tiny hard to find typo in the attachment of the resize event: instead of window.addEventListener('resize', this.onResize()) you need to use window.addEventListener('resize', this.onResize); removing the () because you don't want to call the function at the time of the attachment, you want it called each time the event is triggered.
Due to the first issue, as the resize function wasn't getting called when you expected, I guess this is what led you to use a hard-coded value (1.686275) in the camera instantiation instead of the recommended formula container.clientWidth / container.clientHeight so you need to change that back to
createCamera: function () {
let container = document.getElementById('menu3D');
this.camera = new Three.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.01, 1000);
...
Also as the 3D container div is not in the root level of the HTML body due to VueJS requirements, in onMouseMove() you need to consume the offset coordinates instead of the client ones as follows:
onMouseMove: function (event) {
let container = document.getElementById('menu3D');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = - (event.offsetY / container.clientHeight) * 2 + 1;
...
I'm afraid the problem originates from the creation of your camera:
this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);
Why are you using the magical aspect number 1.686275 instead of the actual width/height ratio like you do on resize? This is giving you a different behavior before and after resizing.
This is my best guess at first glance, although I presume there are other instances of hard coded “magic numbers” in your app that need to be re-calculated based on the screen’s width and height. I couldn't possibly read through the 300 lines of code you posted. You should consider isolating the problem to create a minimal working example and add it to your question via a code snippet so we can see your code in action.
I am using React-native-reanimated and react-native-gesture-handler,
I need to trigger a function after animation done, This is my my code:
let a = new Value(1);
let onStateChange = event([
{
nativeEvent: ({state}) =>
block([
cond(
eq(state, State.END),
set(a, runTiming(new Clock(), 1, 0)),
),
]),
},
]);
return <TapGestureHandler onHandlerStateChange={onStateChange}>
I need something like this :
onStateChange = event => {
if (event.nativeEvent.state === State.END) {
alert("I'm being pressed");
}
return block([
cond(
eq(event.nativeEvent.state, State.END),
set(a, runTiming(new Clock(), 1, 0)),
),
]);
},
But not works. :/
There is a call method to handle back from native section to JS.
You can use the call method once the animation is finished (state.END).
This example is for normal animation but the implementation for your case should be similar, notice that when you use call you can pass back parameters to the function you want to use.
function runTiming({ clock, value, dest, afterAnimationActions }) {
const state = {
finished: new Value(0),
position: value,
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: ANIMATION_DURATION,
toValue: dest,
easing: Easing.inOut(Easing.ease),
};
return block([
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.frameTime, 0),
set(config.toValue, dest),
startClock(clock),
]),
timing(clock, state, config),
cond(state.finished, [
stopClock(clock),
call([dest], (d) => afterAnimationActions(d)), // <-- Add this
]),
state.position,
]);
}
With that, you can call runTiming like this:
transY = runTiming({
clock: this.clock,
value: this.from,
dest: this.toValue,
afterAnimationActions: this.afterAnimationActions,
});
And the afterAnimationActions should look like this:
afterAnimationActions = ([dest]) => {
// `dest` is the param we passed using `call`
// The function logic here
};
animation = new Animated.Value(0);
animationSequnce = Animated.sequence(
[Animated.timing(this.animation, { toValue: 1 }),
Animated.timing(this.animation, { toValue: 0, delay: 1000 }),
],
);
startAnimation = () => {
animationSequnce.start();
}
stopAnimation = () => {
animationSequnce.stop();
}
I want to start an animation sequence several times.
I tested it by writing code that calls startAnimation when the button is pressed.
The animation runs on the first run.
When the second button is clicked after the first animation is finished
Cannot read property 'start' of undefined error occurs.
startAnimation = () => {
Animated.sequence(
[Animated.timing(this.animation, { toValue: 1 }),
Animated.timing(this.animation, { toValue: 0, delay: 1000 }),
],
).start();
}
This change to startAnimation will not cause an error, but you will not be able to call stopAnimation because it will call a different AnimationSequnce each time.
What is the best way to use an animation multiple times?
to stop animation
this.animation.stopAnimation()
optional get callBack when the animation stop
this.animation.stopAnimation(this.callback())
or
Animated.timing(
this.animation
).stop();
The first time you call Animated.sequence(), react native creates a variable current, refers to the index of the current executing animation, and stores it locally.
When Animated.sequence().start() finished, the variable current won't be cleaned or reset to 0, so the second time you call start() on the same sequenced animation, the array index out of bound and cause error.
It's a hidden bug of react native, below is the implementation of Animated.sequence
const sequence = function(
animations: Array<CompositeAnimation>,
): CompositeAnimation {
let current = 0;
return {
start: function(callback?: ?EndCallback) {
const onComplete = function(result) {
if (!result.finished) {
callback && callback(result);
return;
}
current++;
if (current === animations.length) {
callback && callback(result);
return;
}
animations[current].start(onComplete);
};
if (animations.length === 0) {
callback && callback({finished: true});
} else {
animations[current].start(onComplete);
}
},
stop: function() {
if (current < animations.length) {
animations[current].stop();
}
},
reset: function() {
animations.forEach((animation, idx) => {
if (idx <= current) {
animation.reset();
}
});
current = 0;
},
_startNativeLoop: function() {
throw new Error(
'Loops run using the native driver cannot contain Animated.sequence animations',
);
},
_isUsingNativeDriver: function(): boolean {
return false;
},
};
};
My solution is to define a custom sequence, and manually reset current to 0 when the sequenced animations finished:
const sequence = function(
animations: Array<CompositeAnimation>,
): CompositeAnimation {
let current = 0;
return {
start: function(callback?: ?EndCallback) {
const onComplete = function(result) {
if (!result.finished) {
current = 0;
callback && callback(result);
return;
}
current++;
if (current === animations.length) {
current = 0;
callback && callback(result);
return;
}
animations[current].start(onComplete);
};
if (animations.length === 0) {
current = 0;
callback && callback({finished: true});
} else {
animations[current].start(onComplete);
}
},
stop: function() {
if (current < animations.length) {
animations[current].stop();
}
},
reset: function() {
animations.forEach((animation, idx) => {
if (idx <= current) {
animation.reset();
}
});
current = 0;
},
_startNativeLoop: function() {
throw new Error(
'Loops run using the native driver cannot contain Animated.sequence animations',
);
},
_isUsingNativeDriver: function(): boolean {
return false;
},
};
};