Drag&Drop signature

Proof of concept using native HTML5 drag&drop

HTML

<div ng-controller="AppCtrl">
  <div class="controls">
    <div>
      scale
      <select class="scale" ng-model="settings.scale">
        <option value="0.6">60%</option>
        <option value="0.8">80%</option>
        <option value="1.0">100%</option>
        <option value="1.2">120%</option>
        <option value="1.4">140%</option>
      </select>
    </div>

    <div>
      <div class="placeholder" draggable="true">
        Signature
      </div>
    </div>
  </div>

  <div class="page" ng-style="{'font-size': (settings.scale * 16) + 'px'}">
    Hello world!

    <div class="placeholder" placeholder-index="{{ $index }}" draggable="true" ng-repeat="placeholder in placeholders" ng-style="{left: (placeholder.x * settings.scale) + 'px', top: (placeholder.y * settings.scale) + 'px'}">
      Signature
    </div>
  </div>
</div>

CSS

@import url('https://fonts.googleapis.com/css?family=Alex+Brush');
.page {
  margin-top: 20px;
  margin-left: 20px;
  width: 32em;
  height: 40em;
  background: #fff;
  -webkit-box-shadow: 1px 1px 2px 0px rgba(0, 0, 0, 0.35);
  -moz-box-shadow: 1px 1px 2px 0px rgba(0, 0, 0, 0.35);
  box-shadow: 1px 1px 2px 0px rgba(0, 0, 0, 0.35);
  padding: 1.5em;
  position: relative;
  overflow: hidden;
}

.page .placeholder {
  position: absolute;
}

.controls {
  margin-top: 20px;
  float: right;
  width: 200px;
}

.controls>div+div {
  margin-top: 30px;
}

.controls .scale {
  text-align: right;
  width: 80px;
}

.placeholder {
  width: 4.2em;
  height: 1em;
  padding: 0.75em;
  font-size: 2em;
  text-align: center;
  font-family: 'Alex Brush', cursive;
  background: white;
  border: 1px dashed #aaa;
}

.placeholder.hidden {
  visibility: hidden;
}

JavaScript

angular.module('app', []);

angular.module('app').controller('AppCtrl', function($scope) {
  function resetDragging() {
    $scope.draggedPlaceholder = null;
    $scope.dragOffset = {
      x: 0,
      y: 0
    };
  }

  function isDroppedOnPage(target) {
    if (target.classList && target.classList.contains('page')) {
      return true;
    }

    return target.parentNode ? isDroppedOnPage(target.parentNode) : false;
  }

  function getElementOffsetOnPage(target) {
    if (target.classList && target.classList.contains('page')) {
      return {
        x: 0,
        y: 0
      };
    }

    if (!target.parentNode) {
      return null;
    }

    var offset = getElementOffsetOnPage(target.parentNode);
    offset.x += target.offsetLeft;
    offset.y += target.offsetTop;

    return offset;
  }

  $scope.settings = {
    scale: '1.0'
  };
  $scope.placeholders = [];
  resetDragging();

  document.addEventListener("dragstart", function(event) {
    if (!event.target.classList.contains('placeholder')) return;

    var element = event.target;
    var index = element.getAttribute('placeholder-index');
    event.dataTransfer.setData("text", 'dragging');

    // Remember which placeholder is dragged + mouse offset
    $scope.$apply(function() {
      $scope.draggedPlaceholder = index !== null ? $scope.placeholders[index] : {};
      $scope.dragOffset.x = event.offsetX;
      $scope.dragOffset.y = event.offsetY;
    });

    if (index === null) {
      // Scale placeholder from controls for dragging
      element.style.fontSize = (2 * $scope.settings.scale * 16) + 'px';
      setTimeout(function() {
        element.style.fontSize = null;
      }, 0);
    } else {
      // Hide dragged placeholder on page
      setTimeout(function() {
        element.classList.add('hidden');
      }, 0);
    }
  }, false);

  document.addEventListener("dragover", function(event) {
    if (isDroppedOnPage(event.target)) {
      event.preventDefault();
    }
  });

 ...