2

I am trying to create a draggable which is inside defined constraints (e.g.: inside a 300*200px container). However, in addition to that, I'd like to create some sort of rubber-band effect for overdragging the draggable. Until now, I did not find any way to approach this in (pure) JavaScript.

Desired output: http://share.framerjs.com/cz3lly4vgoh9/

My current code: CodeSandbox URL

import React, { Component, Fragment } from "react";

const outerWidth = 400;
const outerHeight = 300;

export default class ImagePreview extends Component {
  draggableContainerRef = null;
  state = {
    dragging: false,
    posX: 0,
    posY: 0
  };

  componentWillMount = () => {
    document.addEventListener("mousedown", this.onMouseDown);
    document.addEventListener("mouseup", this.onMouseUp);
  };

  componentWillUnmount = () => {
    document.removeEventListener("mousedown", this.onMouseDown);
    document.removeEventListener("mouseup", this.onMouseUp);
  };

  calculateCenter = r => {
    const { clientWidth, clientHeight } = r;
    console.log({ clientHeight, clientWidth });

    //const centerX = innerWidth / 2 - clientWidth / 2;
    //const centerY = innerHeight / 2 - clientHeight / 2;
    const centerX = outerWidth / 2 - clientWidth / 2;
    const centerY = outerHeight / 2 - clientHeight / 2;

    console.log({ centerX, centerY });

    this.setState({
      posX: centerX,
      posY: centerY
    });
  };

  lastMousePos = {
    x: 0,
    y: 0
  };

  /**
   * Looks whether the mouse in a mouseEvent is on the desired target (.draggable)
   * @returns {boolean} True / False
   * @memberof ImagePreview
   */
  checkTarget = e => {
    if (
      e.target &&
      (e.target.classList.contains("draggable") || e.target.tagName === "SPAN")
    )
      return true;
    return false;
  };

  totalTranlationY = null;

  checkBounds = (pos, dimension, windowDimension) => {
    const posBoundBefore = 0;
    const posBoundAfter = windowDimension - dimension;

    if (pos < posBoundBefore) {
      return posBoundBefore;
    } else if (pos > posBoundAfter) {
      return posBoundAfter;
    }

    return pos;
  };

  /**
   * Mousedown event listener for .draggable
   * Initiates dragging process if zoomed in.
   * @memberof ImagePreview
   */
  onMouseDown = e => {
    if (this.checkTarget(e)) {
      this.lastMousePos = {
        x: e.clientX,
        y: e.clientY
      };

      document.addEventListener("mousemove", this.onMouseMove);
    }
  };

  /**
   * Mousemove event listener for .draggable
   * Moves
   * @memberof ImagePreview
   */
  onMouseMove = e => {
    const { clientX, clientY } = e;
    const { x: initialX, y: initialY } = this.lastMousePos;
    const { posX: lastStateX, posY: lastStateY } = this.state;

    let posX = this.checkBounds(
      lastStateX + (clientX - initialX),
      this.draggableContainerRef.clientWidth,
      outerWidth
    );
    let posY = this.checkBounds(
      lastStateY + (clientY - initialY),
      this.draggableContainerRef.clientHeight,
      outerHeight
    );

    this.lastMousePos = {
      x: clientX,
      y: clientY
    };

    this.setState({
      pose: "zoomedInDampened",
      dragging: true,
      posX,
      posY
    });
  };

  /**
   * Mouseup event listener for .draggable
   * Checks whether image has been dragged around. Otherwise it toggles the pose for zooming in/out and defaults position.
   * @memberof ImagePreview
   */
  onMouseUp = e => {
    document.removeEventListener("mousemove", this.onMouseMove);

    this.setState({ dragging: false });
  };

  ref = r => {
    this.draggableContainerRef = r;
    if (r !== null) this.calculateCenter(r);
  };

  render() {
    const { posX, posY } = this.state;
    return (
      <Fragment>
        <div
          className="draggableContainer"
          style={{
            fontFamily: "sans-serif",
            width: outerWidth,
            height: outerHeight,
            background: "#292929",
            position: "absolute",
            margin: "auto",
            left: 0,
            right: 0,
            top: 0,
            bottom: 0
          }}
        >
          <div
            ref={this.ref}
            className="draggable"
            style={{
              padding: "10px 15px",
              cursor: "grab",
              background: "gray",
              position: "absolute",
              userSelect: "none",
              transform: `translateX(${posX}px) translateY(${posY}px)`
            }}
          >
            <span
              draggable={false}
              style={{ color: "black", fontWeight: "bold" }}
            >
              Drag me!
            </span>
          </div>
        </div>
      </Fragment>
    );
  }
}
Anton D.
  • 221
  • 1
  • 2
  • 10
  • 1
    Do you have your code? thats a very broad question.. there are many libraries to help you with that. Like this one 'react-dnd' https://github.com/react-dnd/react-dnd – AlexZvl Mar 30 '19 at 17:45
  • react-dnd uses HTML5's drag and drop API which cannot give as much visual feedback as in the GIF shown above. My current prototype is way too messy, though if it desired then I'll try to simplify it and add it to the question – Anton D. Mar 30 '19 at 18:25

0 Answers0