With the Meteor library, it handles the "reactive"/"two way binding" interaction styles quite well, if using their (Spacebars) template to generate HTML. However, I'm wanting to do some animated entrances/exits when data changes, and having difficulty getting that to work.
The use case is I have a dashboard gauge widget rendered as an SVG element. When the data driving the gauge updates, I want the needle to animate to its new position, not just snap there.
To have the needle just snap to the new position, I can do something like:
template.html
<template name="gauge">
<svg>
<path id="needle" {{ needleAttrs }} d="..." />
</svg>
</template>
client.js
Template.gauge.helpers({
needleAttrs: function() {
return {
x: 50,
y: MyCollection.findOne().needleValue
};
}
});
This is effectively the same technique used by the SVG clock example that comes with Meteor.
But how can I transition that animation? I was thinking about pushing the new value from the collection into the Session collection such that a separate animate() method could find it, and some use of Tracker.autorun() to make it Reactive, but I haven't been able to get it to trigger properly.
Related
I was wondering how I would go about dragging/sliding left to reveal the timestamp on react native. I am using a flatlist in react-native and I have the timestamp data but I am unsure of how to render the timestamps on slight drag. Anyone have detailed ideas on how to do this. I have included images of current implementation and iMessage slide/drag. This feature is also on Instagram (ios version at least). I should add that I'm not trying to do a drag and drop feature more like just a solution review what is not currently in the view from a slide/drag
Current App
Current iMessage
This is the eventual solution i came up with all credit from #Abe and reading the gesture-handler documentation referenced above. I
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'
const translateX = useSharedValue(0)
const startX = useRef(0)
My flatlist renders elements like this now
<Animated.View
className="mx-2 flex-1 flex-row"
style={sty}
onTouchStart={(e) => {
startX.current = e.nativeEvent.pageX
}}
onTouchMove={(e) => {
const delta = startX.current - e.nativeEvent.pageX
if (delta > 0) {
translateX.value = -delta / 2
}
}}
onTouchEnd={() => {
translateX.value = withSpring(0)
}}>
...
</Animated.View>
The most common way to handle this is with a PanGestureHandler from react-native-gesture-handler and an Animated.View from react-native-reanimated.
Wrap your component in one, and make the View that you want to move into an Animated.View. (The component that the PanGestureHandler wraps also should be an Animated.View.) Create a shared value (from Reanimated) that represents the x offset of the component. Then create a handler method that responds to the drag gesture and changes the shared value. You may want to add some limits, like not scrolling past a certain offset in either direction. Then, use the shared value in an animated style (something like left: offsetX,) that you apply to your Animated.View.
Since you're using this inside a scrollable list, make sure to set the activeOffsetX prop to something like [-5, 5]. This means that if the user is only trying to scroll up and down, your gesture handler won't steal that touch.
The example in the gesture-handler docs should help.
I am attempting to animate a material property that is buried inside of a gltf-model (that has many objects). I can find the parameter and animate it with a tick event, and it looks like this
https://glitch.com/~uv-reveal-wave
It almost works, but animating with formulas is a nightmare. Instead I want to control it with the animation component. Unfortunately, exposing the parameter directly seems impossible, as I have to use getObject3D.traverse() just to locate the object, and then get the parameter.
Instead I have made a custom attribute (in schema) and and animating that attribute. And in the update function, I'm using that attribute to drive the buried parameter. It should work, but I can't seem to get a response in the update function. Iow, my custom attribute is not animating.
AFRAME.registerComponent('matclipplane', { // attached to gltf-model tree
schema:{
clipHeightA:{type: 'number', default: 0}
},
init: function () {
let el = this.el;
let comp = this;
comp.treeModels = [];
el.addEventListener('model-loaded', function(ev){
let mesh = el.getObject3D('mesh');
mesh.traverse(function(node){
if (node.isMesh){
comp.treeModels.push(node);
}
comp.modelLoaded = true;
});
...
update: function(){
console.log("update"); // returns nothing. no update from
animating attributes clipHeightA
let comp = this;
if (comp.modelLoaded){
comp.treeModels[1].material.map.offset.y =
this.data.clipHeightA;
}
}
...
AFRAME.registerComponent("click-listener", { // attached to box button
schema:{
dir:{type:"boolean", default: false}
},
init: function(){
let el=this.el;
let data = this.data;
el.addEventListener("click", function(evt){
let tree = document.querySelector('#tree');
data.dir = !data.dir;
if(data.dir){
el.setAttribute("material",'color', 'orange');
tree.emit("grow");
} else {
el.setAttribute('material','color','#332211');
tree.emit('shrink');
}
});
}
});
<a-entity id="tree" gltf-model="#tree" scale="5 5 5"
animation__grow="property: clipHeightA; from: 0; to: 1;
startEvents: grow; dur: 500"
matclipplane></a-entity>
<a-entity id="button" geometry="primitive: box" material="color:orange;
metalness: 0.5" scale="0.2 0.2 0.2" class="clickable" click-listener></a-
entity>
<a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects:
.clickable"></a-entity>
here is the glitch in progress
https://glitch.com/~uv-reveal
Clicking on the orange cube should launch the uv reveal animation. I expect that calling a custom component attribute should trigger an update event, but the update does not log in the console.
Not sure how to animate a parameter inside a gltf.
Do I have to implement the animejs component directly inside my custom component?
Or can this be done with the animate component?
According to the docs you can animate properties through
object3D like animation="property: object3D.x"
components like animation="property: components.myComp.myProp"
I don't see it mentioned (although it's used in the docs) but it also works when you provide the property as the components attribute:
animation__grow="property: matclipplane.clipHeightA;"
glitch here. You can see update is being called.
Also tree was an id of both the asset and the component, therefore grow was emitted on the asset item.
I am writing a mobile app with Ionic 3 framework.
I want to change the color of an SVG when an user hover above a component with its finger. But the color needs to stay when he hover out of the component. It is like if he would be painting with his finger.
I tried CSS hover but it reverse back after leaving the component.
I tried the gestures event of ionic, but it seems that none of them help me.(https://ionicframework.com/docs/components/#gestures).
I tried Angular "mouseover" event. It works great on a browser, but not at all with finger on mobile or mobile simulator.
On click event, I use [style]=myVariable in the .html file. Where "myVariable" is fill=#ffffff for white, or fill=#000000 for black in the Typescript file.
Anyone has an idea on how to achieve this ?
Ok, I found THE solution.
This is pure web.
The idea is to use basic web event "touchstart" and "touchmove".
And use the event to get coordinates of the finger.
Then use function "document.elementFromPoint(x, y)" to get the element under the finger.
It should have been possible to directly access the "event.target" but, unfortunatly, the element returned is always the element which was under the finger during the "touchstart" event.
Then, I change the CSS dynamically.
We have to use "DomSanitizer.bypassSecurityTrustStyle()" to do so... This must not be done if you use an input from the user in the CSS. In my case, possible values are in an hard coded array.
So, read code above.
color-changing-svg.html
<svg id="colorChanginSVG" version="1.1" (touchstart)="handleMove($event) (touchmove)="handleMove($event)">
<g transform="translate(-24.379463,-109.90178)">
<path
id="pixel"
d="some-path"
[style]=pixelColor />
</g>
</svg>
color-changing-svg.ts
#Component({
selector: 'page-color-changing-svg',
templateUrl: 'color-changing-svg.html',
})
export class ColorChanginSvgPage {
possibleColors: Array<string> = ["#ffffff", "#000000"]
currentFingerColor="#0000ff";
pixelColor:string="fill="+"#ff0000"
constructor(public sanitizer: DomSanitizer) {
}
handleMove(ev) {
let currentElement = document.elementFromPoint(ev.touches[0].pageX, ev.touches[0].pageY);
let id = currentElement.id
if (id === "pixel"){
this.pixelColor = this.sanitizer.bypassSecurityTrustStyle("fill:" + this.currentFingerColor)
}
}
}
I am using the updated super-hands component in a-frame. I have replicated the basic example from the docs (see below).
<head>
<title>Most Basic Super-Hands Example</title>
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.1.2/dist/aframe-extras.min.js"></script>
<script src="https://unpkg.com/super-hands#3.0.0/dist/super-hands.min.js"></script>
</head>
<body>
<a-scene>
<a-assets></a-assets>
<a-entity>
<a-camera></a-camera>
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="left"></a-entity>
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="right"></a-entity>
</a-entity>
<!-- hover & drag-drop won't have any obvious effect without some additional event handlers or components. See the examples page for more -->
<a-box hoverable grabbable stretchable draggable dropppable color="blue" position="0 0 -1"></a-box>
</a-scene>
</body>
Which works well out of the box when it comes to grabbing objects and being able to affect their position, but not their rotation. I wondered if it was possible, (without using physics and adding a static body to the controller) to affect the rotation of the targeted objects?
I have been able to get the rotation working by adding the relevant mixin and phase-shift component from this example but given it seems possible to grab objects affecting their position I wondered if I could modify the basic example to change the rotation instead of the position (ideally I would be able to choose whether to affect position, rotation or both). I guess I'm imagining perhaps creating another component like rotatable or maybe a build on the grabbable component like grabbable="property: rotation" that I could add to the target objects.
I would like to know if this is possible in general as I would like to understand the component better so as to have more control. To give some context for this particular question however, I have a situation where the super-hands controllers are children of a dynamic-body and once a static-body is added, it causes problems with the behaviour of the parent dynamic-body.
Any advice appreciated as ever, if you need any more info, please let me know.
If you want the rotation without physics, you'll need to implement your own version of grabbable that does the matrix math. It would go something like this:
tick: (function () {
const grabeeMatrix = new window.THREE.Matrix4()
const ignoreScale = new window.THREE.Vector3()
return function () {
if (this.grabber) {
grabeeMatrix.multiplyMatrices(
this.grabber.object3D.matrixWorld,
this.grabOffsetMatrix
)
grabeeMatrix.multiplyMatrices(this.parentOffsetMatrix, grabeeMatrix)
// using decomp over direct Object3D.matrix manipulation
// keeps in sync with other A-Frame components
grabeeMatrix.decompose(
this.el.object3D.position,
this.el.object3D.quaternion,
// let stretchable manage scale
ignoreScale
)
}
}
})(),
resetGrabber: function () {
if (!this.grabber) {
return false
}
this.grabber.object3D.updateMatrixWorld()
this.el.object3D.parent.updateMatrixWorld()
// save difference between grabber world matrix and grabee world matrix
this.grabOffsetMatrix
.getInverse(this.grabber.object3D.matrixWorld)
.multiply(this.el.object3D.matrixWorld)
// save difference between grabee world and local matrices
this.parentOffsetMatrix.getInverse(this.el.object3D.parent.matrixWorld)
return true
},
I have a text element and a skybox in my scene. When the scene initialises, I want the text to animate its position once.
<!-- scene elements -->
<a-scene coogee2006-scene embedded style="width:100%;height:400px;">
<a-assets>
<img
id="coogee2006"
src="/assets/vr/sydney-coogee-3-peter-gawthrop.jpg"
preload="auto">
<audio
id="beachsound"
src="/assets/vr/beach1.wav"
preload="auto">
</a-assets>
<a-sky src="#coogee2006"
rotation="0 -90 0">
</a-sky>
<!-- text animates in on startup (see searise_vr.js) -->
<a-text
id="coogee2006-text"
value="Coogee, Sydney\n2006"
position="5 12.5 -50"
rotation="0 -15 0"
scale="20 20 20"
visible="true"
text="anchor:align;alphaTest:0.2;width:5;
value:COOGEE, SYDNEY\n2006;
zOffset:0;color:#000;font:exo2bold"
sound="src:#beachsound;autoplay:true;loop:true;volume:20;">
<a-animation
attribute="position"
dur="3000"
begin="coogeetour"
to="12.5 12.5 -50"
easing="ease-in"
fill="both"
repeat="0">
</a-animation>
</a-text>
</a-scene>
If I set a static delay with begin=5000, it works fine, but if I try to set it on an event, like begin="coogeetour", the animation doesn't occur. I've tried firing the event two ways:
First, by registering a component for the scene, in a script tag above the a-scene tag, and using document.querySelector() identify the text element:
<script>
AFRAME.registerComponent('coogee2006-scene', {
// emit text events when the scene is initialised
init: function () {
console.log('Running coogee2006-scene.init()');
document.querySelector("#coogee2006-text").emit('coogeetour');
}
});
</script>
Second, by registering a component for the text element and using this.el, as in the A-Frame Writing a Component section, and putting this in an external file that is linked:
AFRAME.registerComponent('coogee2006-text', {
// emit text events when the scene is initialised
init: function () {
console.log('Initialising text element');
this.el.emit('coogeetour');
}
});
In either case, the console.log works, so the component is initialising, but the animation isn't happening. I can't find coogeetour in the elements' event listeners when debugging, but I don't know if that's because emit() isn't working properly or because it oughtn't show up in the debugging.
EDIT: here's my console log on loading:
Navigated to http://127.0.0.1:4000/private/JoQyfM/
index.js:73A-Frame Version: 0.5.0 (Date 10-02-2017, Commit #110055d)
index.js:74three Version: ^0.83.0
index.js:75WebVR Polyfill Version: dmarcos/webvr-polyfill#a02a8089b
browser.js:117 core:a-assets:warn Asset loading timed out in +0ms 3000 ms
three.js:19590 THREE.WebGLRenderer 83
(index):81 Running coogee2006-scene.init()
browser.js:117 components:sound:warn All the sounds are playing. If you need to play more sounds simultaneously consider increasing the size of pool with the `poolSize` attribute. +118ms
three.js:17507 THREE.WebGLRenderer: image is not power of two (5980x2990). Resized to 8192x4096 <img crossorigin="anonymous" src="/assets/vr/sydney-coogee-3-peter-gawthrop.jpg">
I am not sure exactly why, but it looks like the component methods are swallowing the emit() event. I tried putting that same call in the update() function but it still did not work.
What did work was to put the emit call in a timeout:
setTimeout(function() { document.querySelector("#coogee2006-text").emit('coogeetour'); }, 0);
Codepen: http://codepen.io/shanabus/pen/zZYBaa
Okay, it looks like begin only works with numerical values; delay works with both numerical values and event names. To be fair, this is described in the attribute table for animations, but the block below it on the begin attribute shows an example of it being used with an event name. Maybe a depreciated attribute?
EDIT: okay, maybe this isn't the answer. I'm not entirely sure why delay and begin both exist—is it so there can be a delay following an event trigger, or is delay just depreciated?