We're using react-konva, which renders a <Stage> component that manages an underlying canvas/context.
We want to use canvas2svg with it, which is basically a wrapper around a canvas context that tracks all draws to the canvas and maintains an svg representation for when you're ready to access it. It doesn't modify the canvas api, so in theory, Konva would be unaffected in that its canvas draw calls would be the same - they'd just need to be on the context generated by canvas2svg, rather than the one konva automatically generates.
We're looking for something like this, but it doesn't seem to exist. Are there any ways we could use a ref or otherwise hack Konva into using a C2S context? Or maybe we're missing a built-in property.
var c2sContext = new C2S(500,500);
<Stage context={c2sContext}>
canvas2svg doesn't actually work alongside a normal rendered canvas (it's an alternative to one - for example, the arc() method just renders to svg, not svg + canvas), so you need to replace the render context with a c2s instance, call render so the methods like arc() get called, and then set it back.
You can use a ref on the konva Layer and run code like the following on a button press:
setTimeout(() => {
var oldContext = this.layerRef.canvas.context._context;
var c2s = this.layerRef.canvas.context._context = C2S({...this.state.containerSize, ctx: oldContext});
this.forceUpdate();
setTimeout(() => {
console.log(c2s.getSerializedSvg());
this.layerRef.canvas.context._context = oldContext;
this.forceUpdate();
})
}, 5000);
Two possible solutions that skirt around the need for canvas2svg:
Generating an SVG string manually without rendering svg elements
Rendering svg elements rather than canvas elements, and converting the dom to an html string
Obviously #2 has performance issues and #1 requires messier code - and #2 still requires some additional code/messiness, but both are good options to be aware of.
Related
I currently have a Radar chart in chart.js using the react integration.
I was suprised to note that, when I updated the data, instead of showing a completely new plot, it just transitioned smoothly from the previous dataset to the new one.
What I am interested in is to understand how it works under the hood, which honestly I can't understand, at least from looking at the code.
First: my understanding of React is that, when a prop or state changes, it computes the new DOM, and then merges the new DOM and the current DOM, applying only the difference between the two. However, chartjs seem to be implemented as a Canvas element.
The chartjs integration with react does not do much. Taking the Radar plot, this is what it does
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
which is nothing but declare a <Chart> element and leave it to ChartJS to plot it. In fact, in ChartJS, we have this code, which basically manages the Canvas element and it is smart to perform transitions using animations and so on. This I understand (relatively): a lot of animation and transition helper functions, but this makes sense to me. However, this part is pure JavaScript. There's nothing that is aware of React.
What does not make sense is therefore how the react synchronization system is integrated with this JavaScript library so that the state invalidation of the props/state is synchronised to an animation, instead of a complete rewrite of the Canvas element. I don't seem to find where this magic happens in react-chartjs-2.
As you explained the canvas element does not get changed so it gets reused. To animate the chart chart.js itself has an update method. React-chartjs-2 uses a useeffect function that checks if the data you pass it has changed. If this is the case it calls the update function from chart.js itself and they handle the animations and updates itself:
useEffect(() => {
if (!chartRef.current) return;
if (redraw) {
destroyChart();
setTimeout(renderChart);
} else {
chartRef.current.update();
}
}, [redraw, options, data.labels, data.datasets]);
https://github.com/reactchartjs/react-chartjs-2/blob/4a010540ac01b1e4b299705ddd93f412df4875d1/src/chart.tsx#L78-L87
This is my understanding of the whole process after diving into the code base quite a bit. I've tried to be as detailed as possible with links to the exact line of code I am talking about. Hope this helps:
Beginning with the code snippet you shared:
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
If you follow the RadarController via the import statement, you see that it is fetched from chart.js
Now we move to the Chart.js code and look for this controller RadarController. It is found in a file called src/controllers/controller.radar.js.
Within that file, you see an update function
This function then calls updateElements with the points information
This function gets the new point position which is then set in properties and passed into the updateElement function
This updateElement function directly takes us to the core.datasetController
Here you see a condition to check if the chart is in directUpdateMode. If not, it calls a function to _resolveAnimations
Within this function, you will see the new Animations(args) object
This eventually brings us to the core.animations file which consists of all the animation related information and processing.
One interesting bit I found here was: this is what seems to be making the beautiful movement of points to the changed location.
You can explore this Animations class further for more detailed understanding
So yeah essentially, it is the js part under the hood that facilitates the smooth transitions and this is how it does it. React code is essentially just like a wrapper of Chart.js calling this update method with the new values.
You can see here: https://github.com/reactchartjs/react-chartjs-2/blob/master/src/chart.tsx
The react-chartjs-2 library creates a component that adds a canvas and when the props update the component creates/updates an internal Chart object that uses the rendered canvas.
From what I saw the animation starts when the props are changed.
The path is props->react-chartjs-2 component->chart object->animation
I want to use some transition effects in my react js app. I am using function components in my app.
How do I include transition effects in app according to the business requirement these days?
I want to use animation such that on every render I can see the effect. It would be great if someone can help me out with an example.
If you want to use a library, I would suggest react-spring
https://react-spring.io/ it is based on spring physics, If you want to read about that more check this out https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/
And there is also another good option which is framer motion https://www.framer.com/motion/ which apparently offers more possibilities maybe out of the box (I personally have never tried it before)
For examples you can check their websites they have good examples.
I'm not sure what effect you are trying to generate.
css can be used by itself to generate animations or transitions.
You want to see the effect on each render?
i.e. You want to tie the effect to the react render cycle?
non-memoized values will change on every render
You could use a simple statement like const trigger = {};
Then react to trigger with a useEffect
useEffect(() => { do something }, [trigger]);
finally, visual effect.. apply a class based on state and use setTimeout to remove the state (and therefore the class)
This could be overly involved for exactly what you are trying to achieve but this works for all possible flows based on the question.
Here is one example with div element is moving to according vertical scroll position .
Look carefully.
First, Set the position using useState and define the window.onscroll function.
const [cardTop, setCardTop] = useState(0);
window.onscroll = function() {
if (window.pageYOffset < 30) {
setCardTop(window.pageYOffset + 'px');
}
};
Second, Set the style's top as state variable.
<div className='card t-card' id='tCard' style={{top:`${cardTop}`}}> ...
Congratulations. It probably act exactly.
It's similar to use Jquery or another Javascript, Only use state variable.
Thanks.
I am not sure if this is an issue of react-leaflet-markercluster, react-leaflet, leaflet, react, or my code.
I have a map with several thousand markers and I am using react-leaflet-markercluster for marker clustering. If I need to update a global state of MapComponent, there is 1-3 seconds delay when this change is reflected.
I created a codesandox with 5000 markers and you can see there 2 use cases with performance issues:
1.) MapComponent is inside react-reflex element, that allows resizing panel and propagates new dimensions (width, height) to MapComponent. If width and height are changed, mapRef.invalidateSize() is called to update map dimensions. Resizing is extremely slow.
2.) If user clicks on Marker, global state selected is updated. It is a list of clicked marker ids. Map calls fitBounds method to focus on clicked marker and also marker icon is changed. There is around 1 second delay.
In my project, if I need to change a MapComponent state, it takes 2-3 seconds in dev mode when changes are reflected and it is just a single rerender of MapComponent and its elements (markers).
I took a look at Chrome performance profile and it seems like most time is spent in internal React methods.
It is possible to fix this by preventing rerendering using memo, which is similar to shouldComponentUpdate, but it makes whole code base too complicated. preferCanvas option doesn't change anything. I am wondering what is a good way to fix these issues.
The main problem I identified in your code is that you re-render the whole set of marker components. If you memoize the generation of those, you achieve a good performance boost; instead of running the .map in JSX, you can store all the components in a const; this way, the .map won't run on every render.
from this
...
<MarkerClusterGroup>
{markers.map((marker, i) => {
...
to something like this
const markerComponents = React.useMemo(() => {
return markers.map((marker) => {
return (
<MarkerContainer .../>
);
});
}, [markers, onMarkerClick]);
return (
<>
<MarkerClusterGroup>{markerComponents}</MarkerClusterGroup>
</>
);
The second refactor I tried is changing the way you select a marker. Instead of determining the selected prop from the selected array for each marker, I put a selected field on every marker object and update it when selecting a marker. Also, I add the position to the onClickHandler args to avoid looking for that in the markers array.
There are some other tweaks I don't explain here so please check my codesandbox version.
https://codesandbox.io/s/dreamy-andras-tfl67?file=/src/App.js
To summarize, I can't animate Konva sprites and apply Konva filters at the same time.
I'm drawing a complex stage using react-konva, and parts of it contains animated images using Sprite component animations. In order to start the animations, I am waiting until the nodes are drawn in the browser, with a bit of a "hack" (setTimeout), and then calling shape.start() on each one to start them.
initiateAnimations = () => {
setTimeout(() => {
let stage = this.stageRef.getStage();
var shapes = stage.find('.canvas-animation');
shapes.map(shape => {
shape.start();
})
},100)
}
I also add a color overlay to these images, depending on properties that I feed into the Sprite components before render. I use the Konva RGBA filter for this - it's not perfect but kind of gets the job done. In order to initiate and draw the filter, Konva needs the images to be re-cached, and so after the nodes are drawn in render(), I use the cache and batchDraw functions for this.
applyEffects = () => {
setTimeout(() => {
let stage = this.stageRef.getStage();
var shapes = stage.find(node => { return node.attrs.id.includes("-f1") ? true : false; });
shapes.map(shape => {
this.applyCache(shape)
})
},100)
}
applyCache(target) {
target.cache();
target.getLayer().batchDraw();
}
I have all of this both in componentDidMount and componentDidUpdate.
The problem I am encountering is that the animations and filters work on their own, but never together, regardless of time delays or which order they are in. In conjunction, only the filter will work. On the other hand, if I run only the animation script, the animations start and run fine. But together, the animation just doesn't start. I think I just don't understand the lifecycle here well enough, but maybe someone can help point me in the right direction?
At the current moment konva#7.0.3 doesn't support caching/filters for Sprite object.
As a workaround you can:
Create Konva.Image instance with the image of sprite and use filters on it.
Use node.toImage() method to convert Konva.Image instance into a native image element. As a result you will have and sprite image but with filters applied
Use resulted native image for the animated sprite
I have a WMSLayer which takes a string of comma-separated layer names to make a call to fetch tiles.
import React, { Component } from "react";
import { WMSTileLayer } from "react-leaflet";
class WMSLayers extends Component {
render = () => (
<WMSTileLayer
format="image/png"
layers={this.props.layers}
url="https://ws.topogrids.de/geoserver/ows"
/>
);
}
This component begins network requests when the layers prop changes. This means as soon as a new layers prop is passed, the current WMSLayer is erased, and new tiles begin to draw as they are received. This of course makes total sense.
What I would like to do is somehow retain the old component until the new tiles are fetched, and then render the new tiles all at once. This way, if I simply turn one more layer on, the current layers do not disappear.
I realize I can accomplish this by creating a separate WMSTileLayer for each layer, but this leads to many API calls per zoom/pan if multiple layers are switched on.
The layer does provide an onLoad event that gets fired when the tiles are done loading, but I don't know how to keep the old layers drawn until this is done, if it's even possible.
Advice would be greatly appreciated!
Also, this is not a duplicate of How to keep old tiles until each new tile is loaded when redrawing a layer in Leaflet? - that question is about using multiple layers with one map, but this question is about using one layer with a map (I realize the WMSTileLayer layers prop name might make this seem confusing)