A draggable and resizeable selection widget in react.js
Created
March 18, 2016 22:19
-
-
Save haakenlid/2913733c802429d9549a to your computer and use it in GitHub Desktop.
redux resize-box
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id=app> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Handle extends React.Component { | |
constructor(props) { | |
super(props) | |
let classNames = [] | |
for (const prop of ['top', 'bottom', 'left', 'right']){ | |
props[prop] && classNames.push(prop) | |
} | |
this.className = classNames.join(' ') | |
this.moveMask = [ | |
props.left, props.top, props.right, props.bottom | |
].map((value)=>(value?1:0)) | |
} | |
handleMouseDown(event) { | |
event.stopPropagation() | |
this.props.dragStart(event, this.moveMask, this.className) | |
} | |
render() { | |
return ( | |
<div | |
className={'handle ' + this.className} | |
onMouseDown={this.handleMouseDown.bind(this)} | |
/> | |
) | |
} | |
} | |
class Canvas extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
isResizing: false, | |
position: props.box, | |
moveDelta: [0,0,0,0], | |
className: '', | |
width: 1e6, | |
height: 1e6, | |
} | |
} | |
componentDidMount(){ | |
const r = this.refs.canvas.getBoundingClientRect() | |
this.setState({ | |
height: r.height, | |
width: r.width, | |
}) | |
} | |
getBox() { | |
const p = this.state.position | |
const d = this.state.moveDelta | |
const numsort = (a, b) => (a - b) | |
let horizontal = [ | |
0, this.state.width, p[0] + d[0], p[2] + d[2]].sort(numsort) | |
let vertical = [ | |
0, this.state.height, p[1] + d[1], p[3] + d[3]].sort(numsort) | |
const box = [ | |
horizontal[1], | |
vertical[1], | |
horizontal[2], | |
vertical[2], | |
] | |
return box | |
} | |
newBox(event) { | |
event.stopPropagation() | |
const r = this.refs.canvas.getBoundingClientRect() | |
const x = event.pageX - r.left | |
const y = event.pageY - r.top | |
this.setState({ | |
position: [x,y,x,y] | |
}) | |
this.dragStart(event, [1,1,0,0], 'top left') | |
} | |
dragStart(event, dragMask, className) { | |
this.componentDidMount() | |
this.setState({ | |
isResizing: true, | |
dragStart: [event.pageX, event.pageY], | |
dragMask: dragMask, | |
className: className, | |
}) | |
} | |
dragMove(event) { | |
let dx = event.pageX - this.state.dragStart[0] | |
let dy = event.pageY - this.state.dragStart[1] | |
let dragMask = this.state.dragMask | |
let delta = [dx,dy,dx,dy].map( | |
(value, i)=>dragMask[i]*value) | |
this.setState({ moveDelta: delta }) | |
} | |
dragEnd(event) { | |
this.setState({ | |
isResizing: false, | |
moveDelta: [0,0,0,0], | |
position: this.getBox(), | |
className: '', | |
}) | |
} | |
getHandles(){ | |
const handlestrings = ['top', 'bottom', 'right', 'left', | |
'top left', 'top right', 'bottom right', 'bottom left'] | |
const handles = handlestrings.map(function(propstring){ | |
let props = {} | |
for (let key of propstring.split(/\s+/)){ | |
props[key] = true | |
} | |
return props | |
}) | |
return handles.map((props) => ( | |
<Handle { ...props } dragStart={this.dragStart.bind(this)} /> | |
)) | |
} | |
render() { | |
if (this.state.isResizing) { | |
return ( | |
<div ref="canvas" | |
className={"canvas " + this.state.className} | |
onMouseUp={this.dragEnd.bind(this)} | |
onMouseLeave={this.dragEnd.bind(this)} | |
onMouseMove={this.dragMove.bind(this)} | |
> | |
<Box ref="box" box={this.getBox()} /> | |
</div> | |
) | |
} else { | |
return ( | |
<div ref="canvas" | |
onMouseDown={this.newBox.bind(this)} | |
className="canvas"> | |
<Box | |
dragStart={this.dragStart.bind(this)} | |
ref="box" | |
className="move" | |
box={this.getBox()}> | |
{ this.getHandles() } | |
</Box> | |
</div> | |
) | |
} | |
} | |
} | |
class Box extends React.Component { | |
getStyle() { | |
return { | |
left: this.props.box[0], | |
top: this.props.box[1], | |
height: this.props.box[3] - this.props.box[1], | |
width: this.props.box[2] - this.props.box[0], | |
} | |
} | |
handleMouseDown(event){ | |
event.stopPropagation() | |
this.props.dragStart(event, [1,1,1,1], 'grabbing') | |
} | |
render(){ | |
return ( | |
<div | |
onMouseDown={this.handleMouseDown.bind(this)} | |
className={"box " + this.props.className } | |
style={this.getStyle()} > | |
<svg width="100%" height="100%"> | |
<rect | |
width="100%" height="100%" /> | |
<rect className="dashed" | |
width="100%" height="100%" /> | |
</svg> | |
{ this.props.children } | |
</div> | |
) | |
} | |
} | |
class App extends React.Component { | |
render() { | |
return ( <Canvas /> ) | |
} | |
} | |
ReactDOM.render(<App/>, document.querySelector('#app')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://fb.me/react-0.14.3.min.js"></script> | |
<script src="https://fb.me/react-dom-0.14.3.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body{ | |
height: 100%; | |
padding: 0; | |
} | |
$dash: 8px; | |
@keyframes ants { | |
0% {stroke-dashoffset: 0px;} | |
100% {stroke-dashoffset: 6 * $dash;} | |
} | |
$marching-ants: ants 1s infinite linear; | |
#app { | |
padding: 1em; | |
height: 100%; | |
box-sizing: border-box; | |
border: 1px solid #aaa; | |
background: #bbb; | |
.canvas { | |
overflow: hidden; | |
position: relative; | |
background: #fff; | |
height: 100%; | |
width: 100%; | |
} | |
.box { | |
position: absolute; | |
&:hover rect.dashed { | |
animation: $marching-ants; | |
} | |
svg{ | |
rect{ | |
stroke-width: 1px; | |
fill: none; | |
stroke: #fff; | |
&.dashed { | |
stroke-dasharray: $dash; | |
stroke: #000; | |
} | |
} | |
} | |
} | |
.grabbing { | |
cursor: move; | |
cursor: grabbing; | |
} | |
.move { | |
cursor: move; | |
cursor: grab; | |
} | |
.top, .bottom { | |
cursor: ns-resize } | |
.left, .right { | |
cursor: ew-resize } | |
.top.left, .bottom.right { | |
cursor: nwse-resize } | |
.top.right, .bottom.left { | |
cursor: nesw-resize } | |
} | |
.handle { | |
$handleSize: 20%; | |
background: #ff3; | |
opacity: 0.0; | |
position: absolute; | |
min-width: $handleSize; | |
min-height: $handleSize; | |
top: 0; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
z-index: 1000; | |
&.left { | |
left: -$handleSize / 2; | |
right: unset; | |
} | |
&.right { | |
right: -$handleSize / 2; | |
left: unset; | |
} | |
&.top { | |
top: -$handleSize / 2; | |
bottom: unset; | |
} | |
&.bottom { | |
cursor: ns-resize; | |
bottom: -$handleSize / 2; | |
top: unset; | |
} | |
&.top, &.bottom{ | |
&.left, &.right { | |
z-index: 2000; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment