Skip to content

Instantly share code, notes, and snippets.

@e1blue
Created May 15, 2018 15:30
Show Gist options
  • Select an option

  • Save e1blue/11ec832791797bacb2480ba15b6e665c to your computer and use it in GitHub Desktop.

Select an option

Save e1blue/11ec832791797bacb2480ba15b6e665c to your computer and use it in GitHub Desktop.
React Draggable List
<div id="app"></div>

React Draggable List

I'm still having some issues with requestanimationframe in react, not able to work on mobile because each list element has an active frame running behind. Probably will make another example on how to handle everything on the parent component itself and also maintain 60fps too.

A Pen by Yugam on CodePen.

License.

class Card2 extends React.Component {
constructor(props) {
super(props);
this.state = {
item: null,
no: null,
val: null,
down: false,
vScale: 0.0,
v: 0,
clicked: 0,
posScale: 1,
z: 1,
y: 0,
posy: 0,
newPos: 0,
shadow1: 1,
shadow2: 2,
vs1: 0,
vs2: 0,
ps1: 0,
ps2: 0
};
this.animate = this.animate.bind(this);
this.startLoop = this.startLoop.bind(this);
this.updateScale = this.updateScale.bind(this);
this.updateYaxis = this.updateYaxis.bind(this);
this.updateShadow = this.updateShadow.bind(this);
this.handleDown = this.handleDown.bind(this);
this.handleUp = this.handleUp.bind(this);
this.handleMove = this.handleMove.bind(this);
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
}
componentWillMount() {
this.setState({
item: this.props.item,
val: this.props.item,
y: this.props.item * 100,
no: this.props.item
});
}
componentDidMount() {
this.startLoop();
}
componentWillReceiveProps(newProps) {
if (!this.state.down) {
this.setState({
item: newProps.item,
val: newProps.item
});
}
}
handleDown(e) {
this.setState({
down: true,
z: 4,
posy: e.clientY
});
}
handleTouchStart(e) {
e.persist();
this.setState({
down: true,
z: 4,
posy: e.touches[0].screenY
});
}
handleUp() {
this.setState({
down: false
});
}
handleTouchEnd() {
this.setState({
down: false
});
}
handleMove(e) {
if (this.state.down) {
let newPos = e.clientY - this.state.posy;
this.setState(
{
y: this.state.val * 100 + newPos
},
() => {
if (this.state.y > this.state.item * 100 + 50) {
if (this.state.item + 1 < 4) {
this.setState(
{
item: this.state.item + 1
},
() => {
this.props.handlePos(
this.state.no,
this.state.item,
this.state.item - 1
);
}
);
}
} else if (this.state.y < this.state.item * 100 - 50) {
if (this.state.item - 1 > -1) {
this.setState(
{
item: this.state.item - 1
},
() => {
this.props.handlePos(
this.state.no,
this.state.item,
this.state.item + 1
);
}
);
}
}
}
);
}
}
handleTouchMove(e) {
e.persist();
if (this.state.down) {
let newPos = e.touches[0].screenY - this.state.posy;
this.setState(
{
y: this.state.val * 100 + newPos
},
() => {
if (this.state.y > this.state.item * 100 + 50) {
if (this.state.item + 1 < 4) {
this.setState(
{
item: this.state.item + 1
},
() => {
this.props.handlePos(
this.state.no,
this.state.item,
this.state.item - 1
);
}
);
}
} else if (this.state.y < this.state.item * 100 - 50) {
if (this.state.item - 1 > -1) {
this.setState(
{
item: this.state.item - 1
},
() => {
this.props.handlePos(
this.state.no,
this.state.item,
this.state.item + 1
);
}
);
}
}
}
);
}
}
updateScale() {
if (this.state.down) {
let f = -0.2 * (this.state.posScale - 1.1);
let a = f / 0.8;
this.setState(
{
vScale: 0.4 * (this.state.vScale + a)
},
() => {
this.setState({
posScale: this.state.posScale + this.state.vScale
});
}
);
} else if (!this.state.down) {
let f = -0.2 * (this.state.posScale - 1);
let a = f / 0.8;
this.setState(
{
vScale: 0.4 * (this.state.vScale + a)
},
() => {
this.setState({
posScale: this.state.posScale + this.state.vScale
});
}
);
if (Math.floor(this.state.posScale) === 1) {
this.setState(
{
z: 1
},
() => {
this.setState({
val: this.state.item
});
}
);
}
}
}
updateYaxis() {
if (!this.state.down) {
let f = -0.2 * (this.state.y - this.state.item * 100);
let a = f / 1;
this.setState(
{
v: 0.3 * (this.state.v + a)
},
() => {
this.setState({
y: this.state.y + this.state.v
});
}
);
}
}
updateShadow() {
if (this.state.down) {
let f = -0.2 * (this.state.shadow1 - 16);
let a = f / 0.8;
this.setState(
{
vs1: 0.4 * (this.state.vs1 + a)
},
() => {
this.setState({
shadow1: this.state.shadow1 + this.state.vs1
});
}
);
let f2 = -0.2 * (this.state.shadow2 - 32);
let a2 = f2 / 0.8;
this.setState(
{
vs2: 0.4 * (this.state.vs2 + a2)
},
() => {
this.setState({
shadow2: this.state.shadow2 + this.state.vs2
});
}
);
}
if (!this.state.down) {
let f = -0.2 * (this.state.shadow1 - 1);
let a = f / 0.8;
this.setState(
{
vs1: 0.4 * (this.state.vs1 + a)
},
() => {
this.setState({
shadow1: this.state.shadow1 + this.state.vs1
});
}
);
let f2 = -0.2 * (this.state.shadow2 - 2);
let a2 = f2 / 0.8;
this.setState(
{
vs2: 0.4 * (this.state.vs2 + a2)
},
() => {
this.setState({
shadow2: this.state.shadow2 + this.state.vs2
});
}
);
}
}
startLoop() {
if (!this._frameId) {
this._frameId = window.requestAnimationFrame(this.animate);
}
}
animate() {
this.updateScale();
this.updateYaxis();
this.updateShadow();
document.getElementById("card" + this.state.no).style.transform =
" translate3d(0px ," +
this.state.y +
"px, 0px)scale(" +
this.state.posScale +
")";
document.getElementById("card" + this.state.no).style.zIndex = this.state.z;
document.getElementById("card" + this.state.no).style.boxShadow =
"rgba(0, 0, 0, 0.2) 0px " +
this.state.shadow1 +
"px " +
this.state.shadow2 +
"px 0px";
this.frameId = window.requestAnimationFrame(this.animate);
}
stopLoop() {
window.cancelAnimationFrame(this._frameId);
}
render() {
return (
<div
id={"card" + this.state.no}
className="card"
onMouseDown={this.handleDown}
onMouseUp={this.handleUp}
onMouseMove={this.handleMove}
onMouseLeave={this.handleUp}
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
>
{this.state.no + 1}
</div>
);
}
}
class App2 extends React.Component {
constructor(props) {
super(props);
this.state = {
down: false,
no: 0,
v: 0.0,
pos: 1,
clicked: 0,
// 0, 1, 2, 3
card: [0, 1, 2, 3]
};
this.changePos = this.changePos.bind(this);
}
changePos(i, newPos, oldPos) {
let arr = this.state.card;
for (let j = 0; j < 4; j++) {
if (j != i) {
if (arr[j] === newPos) {
arr[j] = oldPos;
arr[i] = newPos;
}
}
}
this.setState({
card: arr
});
}
render() {
let cards = this.state.card.map((item, i) => {
return (
<Card2 key={i} item={item} y={item * 100} handlePos={this.changePos} />
);
});
return (
<div className="app">
<div className="text">Draggable List</div>
<div className="container">{cards}</div>
</div>
);
}
}
let mountNode = document.getElementById("app");
ReactDOM.render(<App2 />, mountNode);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app {
position: relative;
width: 400px;
height: 100vh;
margin-left: calc(50% - 200px);
padding-top: 30px;
}
.text {
position: relative;
font-family: "Montserrat", sans-serif;
font-size: 40px;
letter-spacing: -1px;
}
.container {
position: relative;
width: 100%;
height: 400px;
margin-top: 30px;
}
.card {
position: absolute;
width: 90%;
height: 90px;
transform-origin: 50% 50% 0px;
will-change: transforms;
background-image: linear-gradient(120deg, #89f7fe 0%, #66a6ff 100%);
border-radius: 4px;
cursor: -webkit-grab;
text-align: center;
color: #fff;
font-family: "Montserrat", sans-serif;
font-size: 52px;
padding-top: 14px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media screen and (max-width: 500px) {
.app {
width: 100%;
margin-left: 0px;
}
.text {
margin-left: 5%;
}
.card {
margin-left: 5%;
}
}
<link href="https://fonts.googleapis.com/css?family=Montserrat:800" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment