Edit in JSFiddle

<canvas id="canv" width="300" height="300"></canvas>
#canv {
  outline: 1px dashed gray;
}
function lerpXY(from, to, t) {
  return {
  	x: from.x * (1 - t) + to.x * t,
    y: from.y * (1 - t) + to.y * t,
  }
}

class Rect {
	constructor(rect, ctx) {
  	this.ctx = ctx;
  	this.type = rect.type || 'static';
    this.fill = rect.fill;
    this.stroke = rect.stroke;
    this.speed = rect.speed || 0;
    this.dragOffset = rect.dragOffset || 0;

  	this.rect = {
    	x: rect.rect[0], y: rect.rect[1],
      width: rect.rect[2], height: rect.rect[3]
    };

    this.current = { x: this.rect.x, y: this.rect.y };
    this.animateFrom = { x: this.rect.x, y: this.rect.y };

    this.target = null;
    this.animateTo = null;

    if (rect.target) {
      this.target = { x: rect.target[0], y: rect.target[1] };
      this.animateTo = { x: rect.target[0], y: rect.target[1] };

      const dist = Math.hypot(this.animateTo.x - this.current.x, this.animateTo.y - this.current.y);
      this.updateDuration();
    }

    this.snapped = true;
    this.beforeMoved = null;
    this.animatinDone = false;
    this.isDragging = false;

    this.render();

  }

  animate(now, playing) {   
    if ((this.type === 'animatable' && !this.animatinDone && playing) ||
    		(this.type === 'draggable' && !this.snapped)) {
      this.begin = this.begin || now;
      let t = Math.min(1, (now - this.begin) / this.duration);
      t = Math.pow(t, 4);
      this.current = lerpXY(this.animateFrom, this.animateTo, t);

      if (t === 1) {
        this.begin = 0;
        this.animatinDone = true;
        this.snapped = true;
      }
    }

    this.render();
  }
  
  snap() {
    const rcx = this.current.x + this.rect.width;
    const rcy = this.current.y + this.rect.width;

    const tcx = this.target.x + this.rect.width;
    const tcy = this.target.y + this.rect.width;
    
    const dx = Math.abs(rcx - tcx);
    const dy = Math.abs(rcy - tcy);

    if (dx < this.dragOffset && dy < this.dragOffset) {
      this.animateTo.x = this.target.x;
      this.animateTo.y = this.target.y;
    }
    else {
    	this.animateTo.x = this.rect.x;
      this.animateTo.y = this.rect.y;
    }
    
    this.animateFrom.x = this.current.x;
    this.animateFrom.y = this.current.y;

    this.updateDuration();
  }

  updateDuration() {
    const dist = Math.hypot(this.animateFrom.x - this.animateTo.x, this.animateFrom.y - this.animateTo.y);
    this.duration = (dist / this.speed * 1000) || 1;
  }

  moveTo(dx, dy) {
  	this.current.x = this.beforeMoved.x + dx;
    this.current.y = this.beforeMoved.y + dy;
  	this.render();
  }

  beforeDrag() {
  	this.isDragging = true;
  	this.snapped = true;
    this.beforeMoved = { ...this.current };
  }

  afterDrag() {
  	this.isDragging = false;
  	this.snapped = false;
    this.begin = 0;
    this.beforeMoved = null;
    this.snap();
  }

  testInside(x, y) {
    const x1 = this.current.x;
    const y1 = this.current.y;
    const x2 = x1 + this.rect.width;
    const y2 = y1 + this.rect.height;
 
 		return x > x1 && x < x2 && y > y1 && y < y2;
  }

  render() {
    this.ctx.save();
    this.ctx.fillStyle = this.fill;
    this.ctx.strokeStyle = this.stroke;

    this.ctx.strokeRect(
    	this.target.x, this.target.y,
      this.rect.width, this.rect.height
    );
    
    this.ctx.fillRect(
    	this.current.x, this.current.y,
      this.rect.width, this.rect.height
    );
    
    if (this.isDragging) {
    	this.ctx.setLineDash([3, 3]);
      this.ctx.strokeRect(
        this.target.x - this.dragOffset, this.target.y - this.dragOffset,
        this.rect.width + this.dragOffset * 2, this.rect.height + this.dragOffset * 2
      );
    }

    this.ctx.restore();
  }
}

class Scene {
  constructor(canvas) {
  	this._canvas = canvas;
    this._ctx = canvas.getContext('2d');
    this._objects = [];
    this._playing = false;

    this._state = {
    	mouse: {
      	x: 0, y: 0,
        sx: 0, sy: 0,
        dx: 0, dy: 0,
        down: false
      },
      draging: null,
    };

    this._init();
  }

  _init() {
  	const coords = (canvas, event) => {
    	const rect = canvas.getBoundingClientRect();
      return {
      	x: event.clientX - rect.left,
        y: event.clientY - rect.top,
      };
    };

  	const ondrag = (event) => {
      const { x, y } = coords(this._canvas, event);

      this._state.mouse.x = x;
      this._state.mouse.y = y;

      if (this._state.mouse.down) {
      	this._state.mouse.dx = x - this._state.mouse.sx;
        this._state.mouse.dy = y - this._state.mouse.sy;
      }
    };

    document.addEventListener('mousedown', (event) => {
      this._state.mouse.down = true;

      const { x, y } = coords(this._canvas, event);
      this._state.mouse.sx = x;
      this._state.mouse.sy = y;
      this._state.mouse.x = x;
      this._state.mouse.y = y;
      this._getDragging();

      document.addEventListener('mousemove', ondrag);
    });

    document.addEventListener('mouseup', (event) => {
      this._state.mouse.down = false;

      if (this._state.draging) {
      	this._state.draging.afterDrag();
        this._state.draging = null;
      }
      
      this._state.mouse.dx = 0;
      this._state.mouse.dy = 0;

      document.removeEventListener('mousemove', ondrag);
    });

    const draw = now => {
    	this._render(now);
    	requestAnimationFrame(draw);
    };
    requestAnimationFrame(draw);
  }

  _render(now) {
  	this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

		for (let i = 0; i < this._objects.length; i++) {
    	const rect = this._objects[i];
      if (rect !== this._state.draging) {
      	rect.animate(now, this._playing);
      }
    }

    if (this._state.draging && this._state.mouse.down) {
    	this._state.draging.moveTo(this._state.mouse.dx, this._state.mouse.dy);
    }
  }

  _getDragging() {
  	const x = this._state.mouse.x;
    const y = this._state.mouse.y;
    this._state.draging = null;

    for (let i = this._objects.length - 1; i >= 0; i--) {
      if (this._objects[i].type === 'draggable' && this._objects[i].testInside(x, y)) {
      	this._state.draging = this._objects[i];
        break;
      }
    }
    
    if (this._state.draging) {
      this._state.draging.beforeDrag();
    }
  }

  addRect(rect) {
		this._objects.push(new Rect(rect, this._ctx));
  }

  play() {
  	this._playing = true;
  }
}

const scene = new Scene(document.querySelector('#canv'));
scene.addRect({
  fill: 'green',
  stroke: '#777',
  type: 'animatable',
  rect: [20, 20, 100, 100],
  target: [20, 150],
  speed: 50,
});

scene.addRect({
  fill: 'red',
  stroke: '#777',
  type: 'draggable',
  rect: [150, 20, 100, 100],
  target: [150, 150],
  speed: 300,
  dragOffset: 25,
});

setTimeout(() => scene.play(), 1000);