Introduction ๐
I was trying to make a clone of codepen in React for that I had to figure out a way to create a resizable window to make a 2-pan resizable layout. Here's a sneak peek of what my solution gave me.
Code Sandbox preview
What we are building
here we will be building a div element and a handle to the right side of it which the user can drag to change the size of the div.
What things we will use
event.Window.offsetX method This method gives us the x coordinates of our cursor
useRef Hook: to get a reference of the handle.
useCallback Hook: to create a memorize function to remove a bug in our implementation.
CSS variable: Track and manipulate the width of the window.
Overview of how we are approaching this
create 2 divs
The first
div
that needs to be resized, it will take itswidth
from a CSS variable--width
which will be declared inline<div styles={{ --width: "20px" }}> </div>
Next, a second div which is a handle, will track on mouseDown and onMouseup events on it to start and stop tracking the cursor.
- onMuseDown we will track
x
coordinates of cursor, then calculate the diff from start till the end then update--width
variables value using useState which will intern change the width of the element.
- onMuseDown we will track
Let's Start Coding ๐ป
Step 1. Create an empty Project
use can init a react project as you like.
After the project has been created, we'll do the basic ui setup
css
// App component
import "./app.css"
function App() {
return(<div className="container">
<div className="box" ></div>
<div className="handle"></div>
</div>)
}
export default App
/*Basic Styling src/app.css */
body {
margin: 0px;
}
.container{
display: flex;
align-items: center;
width: 100vw;
height: 100vh;
background-color: #CAD5E2;
padding: 20px;
}
.box {
background-color: #8D3DAF ;
height: 200px;
width: 200px;
}
.handle {
background-color: #0D0D0D;
height: 200px;
width: 10px;
}
Step 2. Create an object with a style const [width, setWidth] = useState({ "--width": "200px" });
above we have created an variable
width
which is an object that holds css styles,which is then passed to inline css.
and in our app.css file, we can use the variable from that css to set
width
ofbox
Step 3 Create Three functions for mouseUp mouseDown and mouseMove events
handleWindowMouseMove
: this will hold logic for updating the width state, for now, lets just console log the location of the mouse.const handleWindowMouseMove = (event) => { console.log(event.clientX); }
hadnleMouseDown
: this will be triggered when you click and hold you cursor on handle , we will useonMouseDown
event to trigger it.This will add an event listener to the window object which will listen to our mouse movement
function hadnleMouseDown(){ window.addEventListener("mousemove", handleWindowMouseMove); };
hadnleMouseUp
: similar tohandleMouseDown function
it will be triggeredonMouseUp
event from handle, box and container.This will add an event listener to window
function hadnleMouseUp() window.removeEventListener("mousemove", handleWindowMouseMove); }
Now add this event Listeners to concerning elements
Now the handle Mouse Move event will log the location of mouse .
Take the coordinates and put them into styles
use setWidth
to change css variables values.
This works but there is a bug .( We when we leave the mouse button, the event listner is not remove , therefore it will keep changing the sige even after we are not holding the button ).
Cause of this issue : on every re-render the function on muse down is triggerd add added to the event. and at the end only the latest listner is removed from the event and all previous are still there
Solution: we can use useCallback to memorise the handleWindowMouseMove
function. this will persist the handleWindowMouseMove
function across renders.
Implementing useCallback Hook Solution
just wrap the function definition of hadnleWidow...
function.
This solves The issue we previously had but there is still an issue
issue: the width of box is not changing as desired it just randomly jums to a huge with it does not follow the handle properly
Reason: the cordinate we get from event.clientX
is from the far left of the screen , on the other hand, our box starts from another position. this is clear in the digram below
Yup, thats it its this small of code for resizable div ๐
Here is how the final app.jsx looks like
import React, { useCallback, useState } from "react";
import "./app.css";
function App() {
// obj for inline CSS
const [width, setWidth] = useState({ "--width": "200px" });
const handleWindowMouseMove = useCallback((event) => {
// console.log(event.clientX)
setWidth({ "--width": `${event.clientX}px` });
}, []);
function hadnleMouseDown() {
window.addEventListener("mousemove", handleWindowMouseMove);
}
function hadnleMouseUp() {
window.removeEventListener("mousemove", handleWindowMouseMove);
}
return (
<div className="container" onMouseUp={hadnleMouseUp}>
<div className="box" style={width} onMouseUp={hadnleMouseUp}></div>
<div
className="handle"
onMouseDown={hadnleMouseDown}
onMouseUp={hadnleMouseUp}
></div>
</div>
);
}
export default App;
You can use the same concept to make different types of the resizable component by adding handlers to different sides.
Follow me on twitter and Linkdin