<div class="container">
    <div id="content"></div>
</div>
* {
  padding: 0;
  margin: 0;
  user-select: none;
}

body {
  background-color: #efefef;
  overflow: hidden;
}

.container {
  display: flex;
  justify-content: center;
  transform: scale(.80);  
  position: relative;
}

.display {
  width: 375px;
  height: 667px;
  background-color: #3F51B5;
  border-radius: 14px;
}

.apps {
  z-index: 1;
  position: absolute;
  top: 0;
  padding: 24px;
  padding-top: 28px;
}

.homerow-bg {
  background-color: #222;
  width: 375px;
  height: 96px;
  z-index: 0;
  position: absolute;
  top: 571px;
  border-radius: 0 0 14px 14px;
}

.app-icon {
  position: absolute;
  border: 1px solid black;
  border-radius: 14px;
  width: 60px;
  height: 60px;
}
const { Motion, spring, presets } = ReactMotion;

const reinsert = (arr, from, to) => {

    // shallow copy of the array
    const result = arr.slice();

    // value to reinsert
    const val = result[from];

    // remove the value from the result
    result.splice(from, 1);

    // reinsert the value into the result
    result.splice(to, 0, val);

    return result;
};

const clamp = (n, min, max) => {

    // make sure n is between min and max
    return Math.max(Math.min(n, max), min);

};

const colors = [
    '#EF767A', '#456990', '#49BEAA', '#49DCB1', '#EEB868', '#EF767A', '#456990',
    '#49BEAA', '#49DCB1', '#EEB868', '#EF767A',
];

const getColors = (n) => {
    return _.times(n, () => {

        // random index less than the length
        const index = Math.floor(Math.random() * colors.length);

        return colors[index];
    });

};

const springSetting1 = {stiffness: 180, damping: 10};
const springSetting2 = {stiffness: 120, damping: 17};

const [count, width, height] = [28, 88, 88];

const appColors = getColors(28);

// indexed by visual position
const layout = _.times(count, (n) => {
    const row = Math.floor(n / 4);
    const col = n % 4;

    if (n > 23) {
        return [width * col, (height + 5) * row];
    }

    return [width * col, height * row];
});


class App extends React.Component {

    state = {
        mouseXY: [0, 0],
        mouseCircleDelta: [0, 0],
        lastPress: null,
        isPressed: false,
        order: _.times(count, Number)
    };

    componentDidMount() {
    window.addEventListener('touchmove', this.handleTouchMove);
    window.addEventListener('touchend', this.handleMouseUp);
    window.addEventListener('mousemove', this.handleMouseMove);
    window.addEventListener('mouseup', this.handleMouseUp);
    };

    handleTouchStart = (key, pressLocation, e) => {
        this.handleMouseDown(key, pressLocation, e.touches[0]);
    };

    handleTouchMove = (e) => {
        e.preventDefault();
        this.handleMouseMove(e.touches[0]);
    };

    handleMouseMove = ({pageX, pageY}) => {
        // destructure getting the current values from this state
        const {order, lastPress, isPressed, mouseCircleDelta: [dx, dy]} = this.state;

        if (isPressed) {
            const mouseXY = [pageX - dx, pageY - dy];
            const col = clamp(Math.floor(mouseXY[0] / width), 0, 3);
            const row = clamp(Math.floor(mouseXY[1] / height), 0, 6);
            const index = row * 4 + col;
            const newOrder = reinsert(order, order.indexOf(lastPress), index);
            this.setState({mouseXY, order: newOrder});
        }
    };

    handleMouseDown = (key, [pressX, pressY], {pageX, pageY}) => {
        this.setState({
            lastPress: key,
            isPressed: true,
            mouseCircleDelta: [pageX - pressX, pageY - pressY],
            mouseXY: [pressX, pressY]
        });
    };

    handleMouseUp = () => {
        this.setState({
            isPressed: false,
            mouseCircleDelta: [0, 0]
        })
    };

    render() {
        const {order, lastPress, isPressed, mouseXY} = this.state;
        return (
            <div className="display">
                <div className="homerow-bg"/>
                <div className="apps">
                {order.map((_, key) => {
                    let style;
                    let x;
                    let y;
                    const visualPosition = order.indexOf(key);
                    if (key === lastPress && isPressed) {
                        [x, y] = mouseXY;
                        style = {
                            translateX: x,
                            translateY: y,
                            scale: spring(1.2, springSetting1),
                            boxShadow: spring((x - (3 * width - 50) / 2) / 15, springSetting1)
                        };
                    } else {
                        [x, y] = layout[visualPosition];
                        style = {
                            translateX: spring(x, springSetting2),
                            translateY: spring(y, springSetting2),
                            scale: spring(1, springSetting1),
                            boxShadow: spring(0, springSetting1),
                        };
                    }

                    return (
                        <Motion key={key} style={style}>
                            { interpolatedStyle => {
                                return (
                                    <div
                                        onMouseDown={this.handleMouseDown.bind(null, key, [x, y])}
                                        className="app-icon"
                                        style={{
                                            backgroundColor: appColors[key],
                                            transform: `translate3d(
                                            ${interpolatedStyle.translateX}px,
                                            ${interpolatedStyle.translateY}px,
                                            0
                                            )

                                            scale(${interpolatedStyle.scale})`,
                                            zIndex: key === lastPress ? 99 : visualPosition,
                                            boxShadow: `${interpolatedStyle.boxShadow}px 5px 5px rgba(0,0,0,0.5)`,

                                        }}
                                    />
                                )
                            }
                            }

                        </Motion>
                    )
                })}
                </div>
            </div>
        );
    }
}


ReactDOM.render(<App />, document.getElementById("content"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js
  4. https://npmcdn.com/react-motion/build/react-motion.js