Improvements
Massive performance improvements #86
This release involves some of the most comprehensive architectural changes to date - with one goal in mind: speed. Every single step of the drag life cycle has been audited and improved. The improvements are so good that everything feels much smoother to interact with.
Here are some figures that show the performance improvements:
Action | Time before changes | Time after changes | Reduction |
---|---|---|---|
Lifting a Draggable
| 2600ms | 15ms | 99% |
Dragging with small amount of displacement | 9ms | 6ms | 33% |
Dragging with large amount of displacement | 9ms | 6ms | 33% |
Moving into a large list with small amount of displacement | 380ms | 4ms | 99% |
Moving into a large list with large amount of displacement | 380ms | 8ms | 98% |
Time to start animation after a drop | 370ms | 4ms | 99% |
The figures are comprised from single and multi list configurations with a total of 500
Draggables
. Recording was done using development builds and with instrumentation enabled - both of which slows things down. However, the recording was also done on a fairly powerful development machine. Exact improvements will vary depending on size of data set, device performance and so on.
How did we do it? 🤔
I plan on speaking in detail about how we achieved these results at an upcoming React Sydney meetup. You can have a read about the optimisations in detail on our blog post Dragging React performance forward
Great styles out of the box
We now apply some default styles out out of the box for consumption convenience. The styles are fairly reasonable:
cursor: grab
on the drag handle element when you can grab a drag-handlecursor: grabbing
on the body when you are dragginguser-select: none
on the body when you are dragging to avoid selecting any text on the page
There is a new section in the docs which go through this in more detail
✌️ These are also vendor prefixed correctly in accordance with our supported browser matrix. We have also kept things small and still include no css-in-js library.
Obviously you are welcome to override these styles - for example you may want to use different cursors. You can use any mechanism you like to override our styles as long as they have a higher specificity (eg classes, inline styles and so on). This is the first 'opinionated' style to go into the library. Generally we want to avoid making any style decisions for you. However, given that these styles are so common, as so easy to override - we thought a reasonable default would yield the most value.
Published a recommended performance optimisation
We have added a new recommended performance optimisation for Droppable
s which will greatly improve the performance of moving between lists.
Other
- Added peer dependency range for React to
^16.0.0
#249. If you are already using React 16 you will no longer get peer dep warnings. Thanks @drew-walker - Streamlined visibility checks #226
- Bumped dependencies #254
Changes
In order to support our performance improvements we have needed to introduce some breaking api changes. These changes push us closer to the upcoming react 16 api
Draggable > Props
Breaking change 💥
draggableId: string
- type: string
+ index: number
// optionals
isDragDisabled?: boolean
disableInteractiveElementBlocking?: boolean
You now need to provide an index
You will now need to provide an ordered index to a Draggable
. This avoids us needing to do a lot of upfront processing and also will allow us to move to a virtualised solution in the future.
Often the simplest way to grab the index is from a loop that generates the Draggable
list within a Droppable.
Applied to our basic usage example:
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
- {this.state.items.map((item, index) => (
- <Draggable key={item.id} draggableId={item.id}>
+ {this.state.items.map((item, index) => (
+ <Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.droppableProps}
{...provided.dragHandleProps}
style={getItemStyle(
provided.droppableProps.style,
snapshot.isDragging
)}
>
{item.content}
</div>
{provided.placeholder}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
type
no longer required
The type
prop no longer needs to be provided to a Draggable
. It will now be inferred from the parent Droppable
. This was already the existing behaviour. However, for performance reasons we needed consumers to double provide the type
. This is no longer needed. #188
Draggable > DraggableProvided
Breaking change 💥
type DraggableProvided = {|
- draggableStyle: ?DraggableStyle,
+ draggableProps: DraggableProps,
+ // a simple rename to align with the naming of draggableProps
- dragHandleProps: ?DragHandleProvided,
+ dragHandleProps: ?DragHandleProps,
- These two props will be removed in an upcoming React 16 release but are currently required
innerRef: (HTMLElement) => void,
placeholder: ?ReactElement,
|}
+ type DraggableProps = {|
+ // inline style
+ style: ?DraggableStyle,
+ // used for shared global styles
+ 'data-react-beautiful-dnd-draggable': string,
|}
type DraggableStyle = DraggingStyle | NotDraggingStyle;
- // no longer needed as these are applied via data attributes
- type BaseStyle = {|
- WebkitTouchCallout: 'none',
- WebkitTapHighlightColor: 'rgba(0,0,0,0)',
- touchAction: 'manipulation',
- |}
type NotDraggingStyle = {|
- // now applied by data attribute
- ...BaseStyle,
transform: ?string,
- transition: ?string,
+ transition: null | 'none',
- // now applied by data attribute
- pointerEvents: 'none' | 'auto',
|}
export type DraggingStyle = {|
- // now applied with data attribute
- ...BaseStyle,
pointerEvents: 'none',
position: 'fixed',
width: number,
height: number,
boxSizing: 'border-box',
top: number,
left: number,
margin: 0,
transform: ?string,
+ // opting out of global movement style
+ transition: 'none',
zIndex: ZIndex,
|}
Now setting up a draggable is as simple as:
<Draggable draggableId="my-draggable">
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
- style={provided.draggableStyle}
+ {...provided.draggableProps}
{...provided.dragHandleProps}
>
My Draggable!
</div>
{provided.placeholder}
</div>
)}
</Draggable>
When we move to React 16 #202 we will be able to drop the wrapping element, ref, and placeholder ceremony. Then all you will need to do is spread the
provided.draggableProps
on the element you want to be draggable. Boom.
You are still welcome to monkey patch these objects if you like. Also, now if you are using inline styles you will need to patch that correctly:
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{ color: green, ...provided.draggableProps.style }}
>
You will also want to apply the inline style at after you spread
provided.draggableProps
so that it is not overwritten by the spread
Special thanks ❤️
This PR has been a long time coming #86. It was also a huge effort to implement #226! Thank you to those of you who have engaged with the issue and given feedback. Thanks to @seancurtis for his great css insights. Also a huge thank you to @jaredcrowe for his suggestions, brainstorming, rubber ducking and being generally encouraging.