How to get useChain to run animations sequentially? - react-spring

I'm trying to use react-spring to run animation X and then do animation Y. I'm using the useChain feature. It seems to run everything in parallel. What am I doing wrong? This is a super-simple scenario and I'm surprised it doesn't work AND my Googling has not turned up a lot of other people with the same problem and/or a solution. There are bugs on useChain in react-spring but they seem to cover more limited scenarios.
As an aside, I thought all React components needed to start with a capital letter so how does animated.div pass the compiler?
code sandbox simple example
This is my component. The box moves diagonally, not in the X axis and then in the Y axis.
export const Shaker = () => {
const xRef = useSpringRef();
const { x } = useSpring({
from: { x: 0 },
to: { x: 1 },
config: config.molasses,
ref: xRef
});
const yRef = useSpringRef();
const { y } = useSpring({
from: { y: 0 },
to: { y: 1 },
config: config.molasses,
ref: yRef
});
useChain([xRef, yRef]);
return (
<animated.div
style={{
width: "10rem",
height: "10rem",
backgroundColor: "green",
marginLeft: x.to((i) => i * 600 - 300),
marginTop: y.to((i) => i * 600 - 300)
}}
></animated.div>
);
};

They say this is a bug in react-spring but don't provide the number of the duplicate.
https://github.com/pmndrs/react-spring/issues/1605

Related

Is there a way in Vis.js to keep nodes at the same size while zooming in/out?

I'm wondering whether there is or not a way to keep the size of nodes, in a Vis.js network, always at the same size while zooming in/out
I the Options
{
size: 10,
scaling: {
min: 10,
max: 10
}
}
but this setting doesn't work at all, I also tried with
customScalingFunction: function (min,max,total,value) {
return 1;
}
thinking that this function would run any time there is a re-rendering of the network.
This can be achieved by changing the nodes.size and nodes.font.size options when the network is zoomed. Details on these options can be found in the documentation here and details on the zoom event here. As per the documentation the nodes.size option is only applicable to shapes which do not have the label inside of them.
Example snippet adjusting the size on zoom:
// create an array with nodes
let nodes = new vis.DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" },
]);
// create an array with edges
let edges = new vis.DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 },
]);
// create a network
const container = document.getElementById("mynetwork");
const data = {
nodes: nodes,
edges: edges,
};
let options = {
nodes: {
shape: 'dot',
size: 10,
font: {
size: 10
}
}
};
const network = new vis.Network(container, data, options);
// Set the initial node size once before network is drawn
network.once("beforeDrawing", function() {
setNodeSize(network.getScale());
});
// Adjust size on zoom
network.on("zoom", function (params) {
setNodeSize(params.scale);
});
function setNodeSize(scale){
// Update node size dependent on scale
options.nodes.size = (10 / scale);
options.nodes.font.size = (10 / scale);
network.setOptions(options);
}
#mynetwork {
width: 600px;
height: 160px;
border: 1px solid lightgray;
}
<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<div id="mynetwork"></div>
Regarding the scaling options, I don't believe they can be used to achieve this. Scaling is relative to other nodes on the network, not the viewport / zoom. A quick example to illustrate changing the scaling value is https://jsfiddle.net/34b682qs/. Clicking on the button toggles the scaling value set for each node in sequence between 10 and 20. When all nodes have the same value the network scales them all to be the same size regardless of if that value is 10 or 20. Therefore adjusting this value on zoom would have no effect.

slideshow stuck framer motion

I want to make a slide show in framer motion and I found that in framer motion docs they have an example slide show like this https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed=&file=/src/Example.tsx, but I found a bug when we drag and double click it, it will be stuck like this picture .
import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import { images } from "./image-data";
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 1000 : -1000,
opacity: 0
};
},
center: {
zIndex: 1,
x: 0,
opacity: 1
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0
};
}
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
return Math.abs(offset) * velocity;
};
export const Example = () => {
const [[page, direction], setPage] = useState([0, 0]);images.
const imageIndex = wrap(0, images.length, page);
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection]);
};
return (
<>
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={page}
src={images[imageIndex]}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.2 }
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(1);
} else if (swipe > swipeConfidenceThreshold) {
paginate(-1);
}
}}
/>
</AnimatePresence>
</>
);
};
I try to solve this problem but still can't fix it, can someone help me?
This looks like a bug of framer-motion.
Up until v1.6.2, everything works fine. The bug seems to occur in all later versions.
There is also an interesting changelog:
[1.6.3] 2019-08-19
Fixed
Ensuring onDragEnd always fires after if onDragStart fired.
Here is a link to the related issue on GitHub, opened by the author of this question.
Until that bug is fixed, here is a workaround that uses Pan events
export default function Carousel() {
const animationConfidenceThreshold = 200; // you have to move the element 200px in order to perform an animation
const [displayed, setDisplayed] = useState(0); // the index of the displayed element
const xOffset = useMotionValue(0); // this is the motion value that drives the translation
const lastOffset = useRef(0); // this is the lastValue of the xOffset after the Pan ended
const elementAnimatingIn = useRef(false); // this will be set to true whilst a new element is performing its animation to the center
useEffect(() => {
// this happens after we have dragged the element out and triggered a rerender
if (elementAnimatingIn.current) {
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
// if the element has animated out to the right it animates in from the left
xOffset.set(
rightPan ? -1 * window.innerWidth - 200 : window.innerWidth + 200
);
// perform the animation to the center
animate(xOffset, 0, {
duration: 0.5,
onComplete: () => {
xOffset.stop();
},
onStop: () => {
elementAnimatingIn.current = false;
lastOffset.current = xOffset.get();
}
});
}
});
return (
<div className="container">
<motion.div
className="carouselElement"
onPan={(e, info) => {
xOffset.set(lastOffset.current + info.offset.x); // set the xOffset to the current offset of the pan + the prev offset
}}
style={{ x: xOffset }}
onPanStart={() => {
// check if xOffset is animating, if true stop animation and set lastOffset to current xOffset
if (xOffset.isAnimating()) {
xOffset.stop();
lastOffset.current = xOffset.get();
}
}}
onPanEnd={(e, info) => {
// there can be a difference between the info.offset.x in onPan and onPanEnd
// so we will set the xOffset to the info.offset.x when the pan ends
xOffset.set(lastOffset.current + info.offset.x);
lastOffset.current = xOffset.get(); // set the lastOffset to the current xOffset
if (Math.abs(lastOffset.current) < animationConfidenceThreshold) {
// if its only a small movement, animate back to the initial position
animate(xOffset, 0, {
onComplete: () => {
lastOffset.current = 0;
}
});
} else {
// perform the animation to the next element
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
animate(
xOffset,
rightPan ? window.innerWidth + 200 : -1 * window.innerWidth - 200, // animate out of view
{
duration: 0.5,
onComplete: () => {
// after the element has animated out
// stop animation (it does not do this on its own, only one animation can happen at a time)
xOffset.stop();
elementAnimatingIn.current = true;
// trigger a rerender with the new content - now the useEffect runs
setDisplayed(rightPan ? displayed - 1 : displayed + 1);
}
}
);
}
}}
>
<span style={{ userSelect: "none" }}>
{"I am element #" + displayed}
</span>
</motion.div>
</div>
);
}
Check this codesandbox out!

React spring loop won't run with delay

I'm using these styles with react spring, and after it runs one time, it won't run anymore, if I remove the delay it works fine, why is that?
const styles = useSpring({
loop: true,
to: [
{ opacity: 0, color: '#53317e' },
{ opacity: 1, color: 'white' },
],
from: { opacity: 1, color: 'white' },
delay: 1000,
})
lot of time gone but I had same issue.
To fix it, instead of using
const styles = useSpring({
loop: true,
delay: 2000,
from: {...},
to: [{...}, {...}, ...]
});
I used syntax with passing function instead of object
const [styles, api] = useSpring(() => ({ ... }) )
And then delay prop stops disabling my loop

cropping snapshot in react native maps to just routes

I would like to know if anyone knows how to take snapshot of just routes in react native maps, something what ride sharing companies have when a user views previous trips. I am able to take a snapshot using the code below, though I noticed the height, width, region properties do not.
takeLocationSnapshot() {
const { location: { longitude, latitude, } } = this.state;
const snapshot = this.mapView.takeSnapshot({
width: 300, // optional, when omitted the view-width is used
height: 300, // optional, when omitted the view-height is used
region: {
latitude,
longitude,
LONGITUDEDELTA,
LATITUDEDELTA,
},
// iOS only, optional region to render
format: 'jpg', // image formats: 'png', 'jpg' (default: 'png')
quality: 0.7, // image quality: 0..1 (only relevant for jpg, default: 1)
result: 'file' // result types: 'file', 'base64' (default: 'file')
});
snapshot.then((uri) => {
this.props.locationSnapshotChange(uri);
});
}
<View style={styles.imageContentWrap}>
<Image
source={imageSource}
style={styles.imageStyle}
/>
</View>
imageContentWrap: {
flexWrap: 'wrap',
backgroundColor: 'green',
flex: 0.58,
},
imageStyle: {
flexWrap: 'wrap',
width: '100%',
height: '100%',
resizeMode: 'cover',
overflow: 'hidden',
},
Just using Wrapper with fixed height and overflow hidden, and add position absolute, top property to styles of your image

ngx-charts line chart, how to show the line chart with dot for the data point all the time

for ngx-charts line chart, it show the line chart, but there is no dot for the data point.
If you hover the data point, it show a dot for the data pint and also with a label tooltip.
I like to have the line chart to show all the data point with a dot all the time like this.
I need your help on how to show a dot at the data point in ngx-charts line chart
Here is the sample for ngx-chart https://github.com/kedmenecr/cinnamon-angular5-with-ngx-charts
Here is the source code for ngx-chart libary . https://github.com/swimlane/ngx-charts
thanks.
if anyone still needs this feature I workaround this feature with a non-super clean solution but it works with no side effect so far :
custom service to draw the points over liner chart:
import { Injectable } from '#angular/core';
#Injectable()
export class CustomLinerChartService {
/**
* custom: override SVG to have the dots display all the time over the liner chart
* since it's not supported anymore from ngx chart
*/
showDots(chart) {
let index = 0;
const paths = chart.chartElement.nativeElement.getElementsByClassName(
'line-series'
);
const color = chart.chartElement.nativeElement.getElementsByClassName(
'line-highlight'
);
for (let path of paths) {
const chrtColor = color[index].getAttribute('ng-reflect-fill');
const pathElement = path.getElementsByTagName('path')[0];
const pathAttributes = {
'marker-start': `url(#dot${index})`,
'marker-mid': `url(#dot${index})`,
'marker-end': `url(#dot${index})`
};
this.createMarker(chart, chrtColor, index);
this.setAttributes(pathElement, pathAttributes);
index += 1;
}
}
/**
* create marker
*
*/
createMarker(chart, color, index) {
const svg = chart.chartElement.nativeElement.getElementsByTagName('svg');
var marker = document.createElementNS(
'http://www.w3.org/2000/svg',
'marker'
);
var circle = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
svg[0].getElementsByTagName('defs')[0].append(marker);
marker.append(circle);
const m = svg[0].getElementsByTagName('marker')[0];
const c = svg[0].getElementsByTagName('circle')[0];
const markerAttributes = {
id: `dot${index}`,
viewBox: '0 0 10 10',
refX: 5,
refY: 5,
markerWidth: 5,
markerHeight: 5
};
const circleAttributes = {
cx: 5,
cy: 5,
r: 5,
fill: color
};
m.append(circle);
this.setAttributes(m, markerAttributes);
this.setAttributes(c, circleAttributes);
}
/**
* set multiple attributes
*/
setAttributes(element, attributes) {
for (const key in attributes) {
element.setAttribute(key, attributes[key]);
}
}
}
and after your view init and the data is set to the chart call :
#ViewChild('chart') chart: any;
ngAfterViewInit() {
this.customLinerChartService.showDots(this.chart);
}
make sure to have the reference on your chart :
<ngx-charts-line-chart #chart>
UPDATE
you can't rely on ng-reflect-fill class since it just added in development mood so insted provide your colors as array and chose it based on index for example
This simpler approach first posted here also works well:
https://github.com/swimlane/ngx-charts/issues/462#issuecomment-783237600
I suggest first getting a reference to the chart, and looping through the series:
#ViewChild("numberChart", {read: ElementRef, static: false})
numberChartRef: ElementRef;
...
chartRef.nativeElement.querySelectorAll("g.line-series path").forEach((el) => {
el.setAttribute("stroke-width", "10");
el.setAttribute("stroke-linecap", "round");
});
I test the length of the series, and only apply these changes if the length is 1. Also make sure to return the attributes to the defaults though if you don't want extra thick lines for all your charts-
I have a way simpler solution to this, just adding a single field will do the trick for you:
In your component class, set the "dot" property of the series to true:
this.data = [
{
name: 'Series 1',
series: [
{
name: 'Point 1',
value: 9,
},
{
name: 'Point 2',
value: 7,
},
{
name: 'Point 3',
value: 5,
}
],
dot: true
},
{
name: 'Series 2',
series: [
{
name: 'Point 1',
value: 8,
},
{
name: 'Point 2',
value: 6,
},
{
name: 'Point 3',
value: 4,
}
],
dot: true
}
];
This will show dots on the line chart on the respective data points as shown below:
Data points
Highlight Single Point

Resources