I have taken an example of dragging certain div in react js from here
http://jsfiddle.net/Af9Jt/2/
Now it is in createClass and I need to convert it into class Draggable extends React.Component in order to export it into another component. Here is code
APP.JS
import React from 'react';
import './App.css';
import Draggable from './Draggable.js';
function App() {
return (
<React.Fragment>
<Draggable />
</React.Fragment>
);
}
export default App;
Draggable.js
import React from 'react';
export class Draggable extends React.Component{
constructor(props) {
super(props);
this.state = {
pos: {x: 0, y: 0},
dragging: false,
rel: null
};
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// we could get away with not having this (and just having the listeners on
// our div), but then the experience would be possibly be janky. If there's
// anything w/ a higher z-index that gets in the way, then you're toast,
// etc.
// componentDidUpdate(props, state) {
// if (this.state.dragging && !state.dragging) {
// document.addEventListener('mousemove', this.onMouseMove)
// document.addEventListener('mouseup', this.onMouseUp)
// } else if (!this.state.dragging && state.dragging) {
// document.removeEventListener('mousemove', this.onMouseMove)
// document.removeEventListener('mouseup', this.onMouseUp)
// }
// }
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
console.log("1")
console.log(this.state);
if (e.button !== 0) return
this.setState({
dragging: true,
rel: {
x: e.pageX - e.nativeEvent.offsetX,
y: e.pageY - e.nativeEvent.offsetY
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({dragging: false})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
render() {
return(
<div
style={{position: "absolute", left: "175px", top: "65px", border: "2px solid rgb(170, 170, 85)", padding: "10px"}}
className="my-draggable" data-reactid=".r[2zxee]" id="messi"
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onMouseDown={this.onMouseDown}
initialPos = {{x:0,y:0}}
>
Drag Me! See how children are passed through to the div!
</div>
)
}
}
export default Draggable;
Everything runs fine in this code the box is shown but I cannot drag the div, I couldn't figure out what issue is this. How Can I Solve this?
Here is my sample code in jsfiddle
https://jsfiddle.net/6vdurk79/3/
There were a few things I noticed when converting this into a React.Component:
You never used the this.state.pos when rendering, so even if the position changed in the variables, it wouldn't move the div. The style attribute of the <div> is just hard-coded with { left: "175px", top: "65px" }
You didn't properly get the position of the mouse in your this.onMouseDown function, which caused it to forced every movement to be at the corner.
You never bound this.onMouseMove to anything. Uncommenting the big chunk of commented out code fixed this.
The initialPos attribute you place inside the <div> does absolutely nothing. I converted that into a prop in the constructor.
Here's the updated JSFiddle link: https://jsfiddle.net/ogy4xd1c/3/
And I'll embed it here on StackOverflow in a snippet.
class Draggable extends React.Component {
constructor(props) {
super(props);
this.state = {
pos: props.initialPos || {
x: 0,
y: 0
},
dragging: false,
rel: null
}
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
if (e.button !== 0) return
const de = document.documentElement;
const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
const top = box.top + window.pageYOffset - de.clientTop;
const left = box.left + window.pageXOffset - de.clientLeft;
this.setState({
dragging: true,
rel: {
x: e.pageX - left,
y: e.pageY - top,
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({
dragging: false
})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
componentDidUpdate(props, state) {
if (this.state.dragging && !state.dragging) {
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
}
}
render() {
return ( <div
style={{
position: "absolute",
left: this.state.pos.x,
top: this.state.pos.y,
border: "2px solid rgb(170, 170, 85)",
padding: "10px"
}}
className="my-draggable"
data-reactid=".r[2zxee]"
id="messi"
onMouseDown={this.onMouseDown}
className="my-draggable"
>
Drag Me! See how children are passed through to the div!
</div>
)
}
}
ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }} />, document.querySelector("#root"));
.my-draggable {
cursor: pointer;
width: 200px;
height: 200px;
background-color: #cca;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If you want to pass in children, you can do that too with this modified version: https://jsfiddle.net/hceLjz90/
class Draggable extends React.Component {
constructor(props) {
super(props);
this.state = {
pos: props.initialPos || {
x: 0,
y: 0
},
dragging: false,
rel: null
}
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
if (e.button !== 0) return
const de = document.documentElement;
const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
const top = box.top + window.pageYOffset - de.clientTop;
const left = box.left + window.pageXOffset - de.clientLeft;
this.setState({
dragging: true,
rel: {
x: e.pageX - left,
y: e.pageY - top,
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({
dragging: false
})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
componentDidUpdate(props, state) {
if (this.state.dragging && !state.dragging) {
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
}
}
render() {
return ( <div
style={{
position: "absolute",
left: this.state.pos.x,
top: this.state.pos.y,
border: "2px solid rgb(170, 170, 85)",
padding: "10px"
}}
className="my-draggable"
data-reactid=".r[2zxee]"
id="messi"
onMouseDown={this.onMouseDown}
className="my-draggable"
>
{this.props.children}
</div>
)
}
}
ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }}>
<h1>This is a child element</h1>
<p>This is also a child element</p>
</Draggable>, document.querySelector("#root"))
.my-draggable {
cursor: pointer;
width: 200px;
height: 200px;
background-color: #cca;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
I am trying to make a draggable button using react.
The button drags over the page in a proper manner but when I drop it. Its top and left values become negative(not even reset to their original top:0,left:0) i.e. the component goes out of the page.
code sand box link : code
main draggable.js component:
import React, { Component } from 'react';
import { Button } from '#material-ui/core';
class DraggableButton extends Component {
constructor() {
super();
this.state = {
dragging: false,
diffX: 0,
diffY: 0,
style: {
top: 0,
left: 0
}
}
}
handleMouseDown = (event) => {
console.log("element caught");
this.setState({
diffX: event.clientX - event.currentTarget.getBoundingClientRect().left,
diffY: event.clientY - event.currentTarget.getBoundingClientRect().top,
dragging: true
})
}
handleMouseMove = (event) => {
if (this.state.dragging) {
console.log("dragging");
let left = event.clientX - this.state.diffX;
let top = event.clientY - this.state.diffY;
this.setState({
style: {
left,
top
}
}, console.log("style ", this.state.style))
}
}
handleMouseUp = () => {
console.log('element released');
console.log('left value ', this.state.style.left);
console.log('top value ', this.state.style.top);
this.setState({
dragging: false,
})
}
render() {
return (
<Button
variant="contained" color="primary"
style={{ position: "absolute", ...this.state.style }}
draggable={true}
onDragStart={this.handleMouseDown}
onDrag={this.handleMouseMove}
onDragEnd={this.handleMouseUp}
// onMouseDown={this.handleMouseDown}
// onMouseMove={this.handleMouseMove}
// onMouseUp={this.handleMouseUp}
>
draggable button
</Button>
);
}
}
export default DraggableButton;
console screenshot :
As is visible in the image above at the time of dragging top: 193 left : 309 and as we dropped the element it turned to left: -109 top: -13.
why is this happening how can we fix it ?
In your handleMouseMove you need to check if event.clientX is a positive integer and then change the state, or else it will reduce the diffX value and it will be nevative. (On drag release this becomes 0)
let left = event.clientX - this.state.diffX;
handleMouseMove = (event) => {
if (this.state.dragging) {
let left = event.clientX - this.state.diffX;
let top = event.clientY - this.state.diffY;
if (event.clientX !== 0)
this.setState({
style: {
left,
top
}
});
}
};
I have a series of <StrategyComponent> elements that are rendered through a button click and are fully draggable on the page. Currently the intial positions are static and all components will be rendered to the same position through the initialPos prop. However, since the user is free to move these wherever they want, I want to render each new component that the user adds a certain distance to the right of the right most <StrategyComponent> at the time (wherever that may be). How can I access this component or what is the best way to keep track of the right most bound in which to place incoming components.
Any help with this would be great. I'm sure it can be done, but I'm just getting used to React. Thanks!
StrategyComponent.js
class StrategyComponent extends Component {
constructor(props) {
super(props);
// creates a reference to wrapper div
this.wrapper = React.createRef();
}
state = {
pos: this.props.initialPos,
dragging: false,
rel: null, // position relative to the cursor
};
componentDidUpdate = (props, state) => {
if (this.state.dragging && !state.dragging) {
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
}
};
// calculate relative position to the mouse and set dragging=true
onMouseDown = (e) => {
// only left mouse button
if (e.button !== 0) return;
var pos = this.wrapper.current.getBoundingClientRect();
this.setState({
dragging: true,
rel: {
x: e.pageX - pos.x,
y: e.pageY - pos.y,
},
});
e.stopPropagation();
e.preventDefault();
};
onMouseUp = (e) => {
this.setState({ dragging: false });
e.stopPropagation();
e.preventDefault();
};
onMouseMove = (e) => {
if (!this.state.dragging) return;
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y,
},
});
this.props.handleParentResize(this.wrapper.current.getBoundingClientRect());
e.stopPropagation();
e.preventDefault();
};
render() {
return (
<div
ref={this.wrapper}
onMouseDown={this.onMouseDown}
draggable
className="component-wrapper"
style={{ left: this.state.pos.x + "px", top: this.state.pos.y + "px" }}
>
<h3> Strategy Component {this.props.name}</h3>
</div>
);
}
}
App.js
class App extends Component {
state = {
numComponents: 1,
};
addStrategyComponent = () => {
this.setState({ numComponents: this.state.numComponents + 1 });
};
render() {
const components = [];
for (var i = 0; i < this.state.numComponents; i++) {
components.push(
<StrategyComponent key={i.toString()} name={i.toString()} initialPos={{ x: 200, y: 200 }} />
);
}
return (
<div className="App">
<button onClick={this.addStrategyComponent}>+</button>
<div className="components-wrapper">{components}</div>
</div>
);
}
}
I 'm new in React and i'm tryind to make draggable background, but no matter how hard I try, nothing happens. I found some code on jQuery, but there many advices that it's bad practice use jQuery in React.
Maybe i make something wrong.
Thanks in advance
Here's my React code
import React from "react";
import "../styles/board.css";
class Board extends React.Component {
constructor(props) {
super(props);
this.state = { mouseCliked: 0, startX: 0, startY: 0 };
}
mouseDown(e) {
this.setState({ mouseCliked: 1, startX: e.clientX, startY: e.clientY });
}
mouseUp(e) {
this.setState({ mouseCliked: 0, startX: e.clientX, startY: e.clientY });
}
mouseMove = (e) => {
let newPosY = e.clientY - this.stateY;
let newPosX = e.clientX - this.stateX;
if (this.state.mouseClicked) {
e.target.style.backgroundPositionX += newPosX;
e.target.style.backgroundPositionY += newPosY;
}
};
render() {
return (
<div
onMouseMove={this.mouseMove.bind(this)}
onMouseUp={this.mouseUp.bind(this)}
onMouseDown={this.mouseDown.bind(this)}
className="background-image"
>
</div>
);
}
}
export default Board;
CSS:
width:300px;
height: 300px;
background-size: 1000px;
background-position-x: 0;
background-position-y: 0;
background-image: url('https://images.unsplash.com/photo-1452723312111-3a7d0db0e024?crop=entropy&dpr=2&fit=crop&fm=jpg&h=750&ixjsv=2.1.0&ixlib=rb-0.3.5&q=50&w=1450.jpg');
}
I have encountered this problem and also checked jQuery - drag div css background
Finally I came up with this solution and seemed working fine.
const imageStyleInitialValue = {
backgroundImage: "",
backgroundPosition: "0 0",
backgroundSize: "0 0",
height: 0,
width: 0,
};
const [startPoint, setStartPoint] = useState({x: 0, y: 0});
const [dragging, setDragging] = useState(false);
const [imageStartPos, setImageStartPos] = useState([0, 0]);
const [imageStyles, setImageStyles] = useState<ImageStyles>(imageStyleInitialValue);
// add onMouseMove={handleDragImage} to the image component
const handleDragImage = (e) => {
if (dragging) {
const deltaX = e.clientX - startPoint.x;
const deltaY = e.clientY - startPoint.y;
setImageStyles({...imageStyles,
backgroundPosition:
`${imageStartPos[0] + deltaX} ${imageStartPos[1] + deltaY}`
})
}
};
// add onMouseDown={handleStartDragImage} to the image component
const handleStartDragImage = (e) => {
setDragging(true);
const backgroundPosArray = imageStyles.backgroundPosition.split(" ").map(value => Number(value));
setImageStartPos(backgroundPosArray);
setStartPoint({x: e.clientX, y: e.clientY});
}
// add onMouseUp={handleEndDragImage} to the top component because you want
// to set Dragging to false when the dragging ends outside of the image
const handleEndDragImage = (e) => {
setDragging(false)
};
Very confused about this one. From the code, If you move the green square you will see the printed values.
I expected all of the code to run before the state is changed and the rerender however from this code example i can see the rerender happens as soon as the function to change state is called.
import React, { useState, useEffect, useRef } from "react";
import styled, { ThemeProvider, css } from "styled-components";
export default function App() {
const pos = useRef({ x: 0, y: 0 });
const offset = useRef({ x: 0, y: 0 });
const myDiv = useRef(null);
const [rerender, setRerender] = useState(0);
console.log("render");
useEffect(() => {
window.addEventListener("mousemove", doSomething);
window.addEventListener("mousedown", doSomethingAgain);
return () => {
window.removeEventListener("mousemove", doSomething);
window.removeEventListener("mousedown", doSomethingAgain);
};
}, [rerender]);
console.log("global", rerender);
function doSomething(e) {
if (e.buttons == 1) {
const newPos = {
x: e.pageX,
y: e.pageY
};
pos.current = newPos;
console.log("first", rerender);
setRerender(rerender + 1);
console.log("second", rerender);
}
}
function doSomethingAgain(e) {
const offsetX = e.pageX - myDiv.current.offsetLeft;
const offsetY = e.pageY - myDiv.current.offsetTop;
offset.current = { x: offsetX, y: offsetY };
}
return (
<div className="App">
<DIV ref={myDiv} off={offset.current} pos={pos.current}></DIV>
</div>
);
}
const DIV = styled.div`
display: inline-block;
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top: ${({ pos, off }) => pos.y - off.y}px;
left: ${({ pos, off }) => pos.x - off.x}px;
`;
I would like to have the material ui drawer's width resizable through a draggable handle.
My current approach is to have a mousevent listener on the whole app which checks if handle was pressed and updates the width according to mouse position on every mouse move.
This however requires a constant mouseevent listener on the whole app which seems to be overkill for a simple resize feature.
Are there better/ recommended ways of doing the resize?
You can use indicator dragger with mousedown on it.
Here for example
// styles
dragger: {
width: '5px',
cursor: 'ew-resize',
padding: '4px 0 0',
borderTop: '1px solid #ddd',
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
zIndex: '100',
backgroundColor: '#f4f7f9'
}
...
state = {
isResizing: false,
lastDownX: 0,
newWidth: {}
};
handleMousedown = e => {
this.setState({ isResizing: true, lastDownX: e.clientX });
};
handleMousemove = e => {
// we don't want to do anything if we aren't resizing.
if (!this.state.isResizing) {
return;
}
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
let minWidth = 50;
let maxWidth = 600;
if (offsetRight > minWidth && offsetRight < maxWidth) {
this.setState({ newWidth: { width: offsetRight } });
}
};
handleMouseup = e => {
this.setState({ isResizing: false });
};
componentDidMount() {
document.addEventListener('mousemove', e => this.handleMousemove(e));
document.addEventListener('mouseup', e => this.handleMouseup(e));
}
...
<Drawer
variant="permanent"
open
anchor={'right'}
classes={{
paper: classes.drawerPaper
}}
PaperProps={{ style: this.state.newWidth }}
>
<div
id="dragger"
onMouseDown={event => {
this.handleMousedown(event);
}}
className={classes.dragger}
/>
{drawer}
</Drawer>
The idea is, when click the dragger, it will resize width Drawer followed mouse move.
Play DEMO.
I would like to add an answer that is more up to date using React Hooks.
You can do it like this, then:
CSS:
sidebar-dragger: {
width: '5px',
cursor: 'ew-resize',
padding: '4px 0 0',
borderTop: '1px solid #ddd',
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
zIndex: '100',
backgroundColor: '#f4f7f9'
}
React (using hooks with refs and states)
let isResizing = null;
function ResizeableSidebar (props) {
const sidebarPanel = React.useRef('sidebarPanel');
const cbHandleMouseMove = React.useCallback(handleMousemove, []);
const cbHandleMouseUp = React.useCallback(handleMouseup, []);
function handleMousedown (e) {
e.stopPropagation();
e.preventDefault();
// we will only add listeners when needed, and remove them afterward
document.addEventListener('mousemove', cbHandleMouseMove);
document.addEventListener('mouseup', cbHandleMouseUp);
isResizing = true;
};
function handleMousemove (e) {
if (!isResizing) {
return;
}
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
let minWidth = 50;
if (offsetRight > minWidth) {
let curSize = offsetRight - 60;
// using a ref instead of state will be way faster
sidebarPanel.current.style.width = curSize + 'px';
}
};
function handleMouseup (e) {
if (!isResizing) {
return;
}
isResizing = false;
document.removeEventListener('mousemove', cbHandleMouseMove);
document.removeEventListener('mouseup', cbHandleMouseUp);
};
return <div className="sidebar-container">
<div
className="sidebar-dragger"
onMouseDown={handleMousedown}
/>
<div>
Your stuff goes here
</div>
</div>;
}
It might be a useResize hook with API to enable resizing and providing current width.
import { useCallback, useEffect, useState } from 'react'
type UseResizeProps = {
minWidth: number
}
type UseResizeReturn = {
width: number
enableResize: () => void
}
const useResize = ({
minWidth,
}: UseResizeProps): UseResizeReturn => {
const [isResizing, setIsResizing] = useState(false)
const [width, setWidth] = useState(minWidth)
const enableResize = useCallback(() => {
setIsResizing(true)
}, [setIsResizing])
const disableResize = useCallback(() => {
setIsResizing(false)
}, [setIsResizing])
const resize = useCallback(
(e: MouseEvent) => {
if (isResizing) {
const newWidth = e.clientX // You may want to add some offset here from props
if (newWidth >= minWidth) {
setWidth(newWidth)
}
}
},
[minWidth, isResizing, setWidth],
)
useEffect(() => {
document.addEventListener('mousemove', resize)
document.addEventListener('mouseup', disableResize)
return () => {
document.removeEventListener('mousemove', resize)
document.removeEventListener('mouseup', disableResize)
}
}, [disableResize, resize])
return { width, enableResize }
}
export default useResize
Then you could decouple resizing logic from your layout component like this:
const Layout = () => {
const { width, enableResize } = useResize(200);
return (
<Drawer
variant="permanent"
open
PaperProps={{ style: { width } }}
>
{drawer}
<div
style={{
position: absolute,
width: '2px',
top: '0',
right: '-1px',
bottom: '0',
cursor: 'col-resize'
}}
onMouseDown={enableResize}
/>
</Drawer>
)
Just use a synthetic event on your handle element. That way, you can avoid the messiness/performance costs of having a universal event listener. Something like the following:
render() {
return (
<div onMouseDown={this.yourResizeFunc}>
</div>
);
}
You can do that with css only, if that fits your need. It's the simplest solution. Look mom, no javascript.
.resizable {
height: 150px;
width: 150px;
border: 1px solid #333;
resize: horizontal;
overflow: auto;
}
<div class="resizable"></div>
Reference on MDN