-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic Rendering Feature (performance improvement) #790
Comments
Interesting. I wonder how it's possible to preserve capability of selecting everything with Ctrl+A with that feature. It seems that in Ace editor all features are implemented from scratch (caret, selection, etc), and Slate is using contenteditable, so it may be more difficult to implement such feature in Slate. |
You could preserve Ctrl+A pretty easily by handling that case specifically. You could have a virtual selection and then a rendered selection. I think the larger question is to what degree would you have to implement features and handle edge cases. This would seem to be a significant change but I think you would be able to handle most of the caret & selection with offsets. |
Hey @egmracer01 I think this sounds very interesting, going to leave it open for people to discuss how it might be done. I haven't done any virtualization with React before, so I have nothing to contribute right now. I'd be curious to know which pieces of the rendering could be avoided with virtualization. i think some of that logic might not be avoided, and there might be lowering-hanging fruit elsewhere? |
one piece of lower hanging fruit might be related to: https://medium.com/missive-app/45-faster-react-functional-components-now-3509a668e69f . To summarize the problem, pure rendering components are simply wrappers around normal components and follow the same code paths. By calling the function From my understanding, nodes with custom types are pure: for example: https://github.com/ianstormtaylor/slate/blob/master/examples/rich-text/index.js#L20 . This means that when we render these components: Line 272 in 17ea3ed
Component is pure, we actually could speed this up by changing it to something like:
|
I would wait for React 0.16.0 and then have a new evaluation on it. |
I just wanted to chime in here with a few points on what I believe is and isn't possible and a really good low hanging fruit IMO. I don't think it's possible to arbitrarily render any part of a Slate Document at a given scroll position because it is difficult, possibly impossible, to tell how much space a Slate block will take until after it is rendered. Ace editor is an editor for monospace text so it is easy to predict how much space text will take before we render it. That said, what could work and could improve performance, is to render the visible portion of the page first before giving control back to the user. The rest of the content could fill in afterwards. I think this might be a good low hanging fruit. Just render up to the visible portion, then use setTimeout to render a few blocks at a time for the rest without blocking the UI. This would give the appearance of being usable quickly on very long documents. The only thing you'd see is the scrollbar getting longer. |
@thesunny good points! For anyone needing this, I would highly recommend looking into other performance improvements in the general rendering case first, because they are probably much lower-hanging fruit. |
https://github.com/bvaughn/react-window Might be a solution to this, but it requires you to hand over the rendering to it: /// fake example
import { VariableSizeList as List } from 'react-window';
function AFastEditor() {
const getItemSize = index => editor.blocks[index].getHeight();
// style is used to absolutely locate elements in the long parent
const getBlock = ({ index, style }) => React.cloneElement(editor.blocks[index], { style });
return (
<List
width={800}
height={600}
itemCount={editor.blocks.length}
itemSize={getItemSize}
>
{getBlock}
</List>
);
} I tried to use import React from 'react';
import { Mark, Value } from 'slate';
import { Set } from 'immutable';
import { VariableSizeList } from 'react-window';
import calculateSize from 'calculate-size';
import AutoSizer from 'react-virtualized-auto-sizer';
interface DeserializeProps {
warperBlockType?: string;
defaultBlock?: string;
defaultMarks?: any[];
delimiter?: string;
toJSON?: boolean;
}
export function deserializeHugeText(inputString: string, props: DeserializeProps = {}): Value {
let { defaultMarks = [] } = props;
const { warperBlockType = 'huge-document', defaultBlock = 'line', delimiter = '\n' } = props;
if (Set.isSet(defaultMarks)) {
defaultMarks = defaultMarks.toArray();
}
defaultMarks = defaultMarks.map(Mark.create);
const json = {
object: 'value' as 'value',
document: {
object: 'document' as 'document',
data: {},
nodes: [
{
type: warperBlockType,
object: 'block' as 'block',
data: {},
nodes: inputString.split(delimiter).map(line => {
return {
type: defaultBlock,
object: 'block' as 'block',
data: {},
nodes: [
{
object: 'text' as 'text',
text: line,
marks: defaultMarks,
},
],
};
}),
},
],
},
};
return Value.fromJSON(json as any);
}
export const windowlongTextPlugin = ({
warperBlockType = 'huge-document',
warperTagName = 'article',
fontSize = '18px',
font = 'Arial',
} = {}) => ({
renderBlock: (props, editor, next) => {
const { attributes, children, node } = props;
switch (node.type) {
case warperBlockType: {
const RenderRow = ({ index, style }) => <div style={style}>{children[index]}</div>;
return (
<AutoSizer>
{({ height, width }) => (
<VariableSizeList
outerElementType={warperTagName}
height={height}
itemCount={children.length}
itemSize={index =>
calculateSize(children[index].props.node.nodes.getIn(['0', 'text']), {
font,
fontSize,
width: `${width}px`,
}).height + 20
}
width={width}
>
{RenderRow}
</VariableSizeList>
)}
</AutoSizer>
);
}
default:
return next();
}
},
});
/// And in the file using this plugin, give Editor a min-height
const StyledEditor = styled(Editor)`
min-height: 100vh;
`; But I found using this does not help slate to gain more performance when loading the ValueJSON, it is still quite slow. |
Rendering is very fast, almost instantly, with or without Slate spends lots of time doing Maybe #2658 can solve this. I'm curious why
|
Interested to know everyones thoughts on a deferred rendering method (Such as described in this article on Twitter Lite) https://medium.com/@paularmstrong/twitter-lite-and-high-performance-react-progressive-web-apps-at-scale-d28a00e780a3 Not as utopian as discussed above, but this could improve perceived rendering performance? Attached from the article:
|
@mmdonaldson I would absolutely love to see this. If the current Slate behaviors could be kept with deferred rendering it'd be fantastic. |
Any Updates to efficiently render Long documents with slate editor here? I think this is important from the UX perspective as the editing experience might be compromised for long documents if the Virtualisation is not there. |
Hi all. I was experimenting with this at the
What is the community best practice on this? Is this feature still under consideration, or maybe I am missing some point? |
Still under consideration, but I don't think we've had a recent PR that would make this work. Would be happy to review or discuss further as it would be a nice improvement. |
I have a proof of concept. Is there still interest in this? slatejs.virtualization.POC.mov |
@pau-not-paul 100% still interest! |
Yes, definitely. Optimizing performance for long Slate documents has been an ongoing challenge in a project I’m involved in. Would you mind sharing your approach @pau-not-paul? Your PoC looks great. |
Do you want to request a feature or report a bug?
Feature.
The proposed idea is to dynamically render content within slate. This means that slate would only render visible blocks and not render blocks hidden within the y-overflow. The benefits that this would provide for performance are huge. By far the slowest part of the initial render is mounting all of the components (if I'm reading the timeline correctly). You could effectively bound the number of dom nodes rendered at any given time and perhaps find other optimizations as a result.
slate-large.zip
What's the current behavior?
All of the components are rendered.
What's the desired behavior?
Only visible (with a given padding of elements to support smooth scrolling
The Ace Editor: https://github.com/ajaxorg/ace
Does this by rendering two divs: a full height "scroller" div and then a "content" div that dynamic adjusts based on scrolls while updating it's content (removing hidden dom nodes and adding newly shown dom nodes).
Here's how the position of the content div updates: https://github.com/ajaxorg/ace/blob/master/lib/ace/virtual_renderer.js#L838
Here's how it calculates the properties of the content div: https://github.com/ajaxorg/ace/blob/master/lib/ace/virtual_renderer.js#L1025
Here's an example of dynamic rendering in react: https://github.com/clauderic/react-tiny-virtual-list
The text was updated successfully, but these errors were encountered: