]> git.sesse.net Git - remoteglot/blobdiff - www/js/chessboard-0.3.0.js
Remove obsolete jQuery version checks.
[remoteglot] / www / js / chessboard-0.3.0.js
index 4b24ec04d5fe3a29845da396f9a1e8c10bb79d57..00f83cff53d0852433f8a98b073077f158519521 100644 (file)
@@ -1,7 +1,8 @@
 /*!
- * chessboard.js v0.3.0
+ * chessboard.js v0.3.0+asn
  *
  * Copyright 2013 Chris Oakman
+ * Portions copyright 2022 Steinar H. Gunderson
  * Released under the MIT license
  * http://chessboardjs.com/license
  *
@@ -198,8 +199,7 @@ cfg = cfg || {};
 // Constants
 //------------------------------------------------------------------------------
 
-var MINIMUM_JQUERY_VERSION = '1.7.0',
-  START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR',
+var START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR',
   START_POSITION = fenToObj(START_FEN);
 
 // use unique class names to prevent clashing with anything else on the page
@@ -215,9 +215,6 @@ var CSS = {
   numeric: 'numeric-fc462',
   piece: 'piece-417db',
   row: 'row-5277c',
-  sparePieces: 'spare-pieces-7492f',
-  sparePiecesBottom: 'spare-pieces-bottom-ae20f',
-  sparePiecesTop: 'spare-pieces-top-4028b',
   square: 'square-55d63'
 };
 var CSSColor = {};
@@ -231,9 +228,7 @@ CSSColor['black'] = 'black-3c85d';
 // DOM elements
 var containerEl,
   boardEl,
-  draggedPieceEl,
-  sparePiecesTopEl,
-  sparePiecesBottomEl;
+  draggedPieceEl;
 
 // constructor return object
 var widget = {};
@@ -242,8 +237,7 @@ var widget = {};
 // Stateful
 //------------------------------------------------------------------------------
 
-var ANIMATION_HAPPENING = false,
-  BOARD_BORDER_SIZE = 2,
+var BOARD_BORDER_SIZE = 2,
   CURRENT_ORIENTATION = 'white',
   CURRENT_POSITION = {},
   SQUARE_SIZE,
@@ -251,7 +245,6 @@ var ANIMATION_HAPPENING = false,
   DRAGGED_PIECE_LOCATION,
   DRAGGED_PIECE_SOURCE,
   DRAGGING_A_PIECE = false,
-  SPARE_PIECE_ELS_IDS = {},
   SQUARE_ELS_IDS = {},
   SQUARE_ELS_OFFSETS;
 
@@ -259,40 +252,15 @@ var ANIMATION_HAPPENING = false,
 // JS Util Functions
 //------------------------------------------------------------------------------
 
-// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
+let id_counter = 0;
 function createId() {
-  return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) {
-    var r = Math.random() * 16 | 0;
-    return r.toString(16);
-  });
+  return 'chesspiece-id-' + (id_counter++);
 }
 
 function deepCopy(thing) {
   return JSON.parse(JSON.stringify(thing));
 }
 
-function parseSemVer(version) {
-  var tmp = version.split('.');
-  return {
-    major: parseInt(tmp[0], 10),
-    minor: parseInt(tmp[1], 10),
-    patch: parseInt(tmp[2], 10)
-  };
-}
-
-// returns true if version is >= minimum
-function compareSemVer(version, minimum) {
-  version = parseSemVer(version);
-  minimum = parseSemVer(minimum);
-
-  var versionNum = (version.major * 10000 * 10000) +
-    (version.minor * 10000) + version.patch;
-  var minimumNum = (minimum.major * 10000 * 10000) +
-    (minimum.minor * 10000) + minimum.patch;
-
-  return (versionNum >= minimumNum);
-}
-
 //------------------------------------------------------------------------------
 // Validation / Errors
 //------------------------------------------------------------------------------
@@ -359,39 +327,12 @@ function checkDeps() {
     }
 
     // set the containerEl
-    containerEl = $(el);
+    containerEl = el;
   }
 
-  // else it must be something that becomes a jQuery collection
-  // with size 1
-  // ie: a single DOM node or jQuery object
+  // else it must be a DOM node
   else {
-    containerEl = $(containerElOrId);
-
-    if (containerEl.length !== 1) {
-      window.alert('ChessBoard Error 1003: The first argument to ' +
-        'ChessBoard() must be an ID or a single DOM node.' +
-        '\n\nExiting...');
-      return false;
-    }
-  }
-
-  // JSON must exist
-  if (! window.JSON ||
-      typeof JSON.stringify !== 'function' ||
-      typeof JSON.parse !== 'function') {
-    window.alert('ChessBoard Error 1004: JSON does not exist. ' +
-      'Please include a JSON polyfill.\n\nExiting...');
-    return false;
-  }
-
-  // check for a compatible version of jQuery
-  if (! (typeof window.$ && $.fn && $.fn.jquery &&
-      compareSemVer($.fn.jquery, MINIMUM_JQUERY_VERSION) === true)) {
-    window.alert('ChessBoard Error 1005: Unable to find a valid version ' +
-      'of jQuery. Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or ' +
-      'higher on the page.\n\nExiting...');
-    return false;
+    containerEl = containerElOrId;
   }
 
   return true;
@@ -438,16 +379,6 @@ function expandConfig() {
     cfg.dropOffBoard = 'snapback';
   }
 
-  // default for sparePieces is false
-  if (cfg.sparePieces !== true) {
-    cfg.sparePieces = false;
-  }
-
-  // draggable must be true if sparePieces is enabled
-  if (cfg.sparePieces === true) {
-    cfg.draggable = true;
-  }
-
   // default piece theme is wikipedia
   if (cfg.hasOwnProperty('pieceTheme') !== true ||
       (typeof cfg.pieceTheme !== 'string' &&
@@ -509,7 +440,7 @@ function expandConfig() {
 // fudge factor, and then keep reducing until we find an exact mod 8 for
 // our square size
 function calculateSquareSize() {
-  var containerWidth = parseInt(containerEl.css('width'), 10);
+  var containerWidth = parseInt(getComputedStyle(containerEl).width, 10);
 
   // defensive, prevent infinite loop
   if (! containerWidth || containerWidth <= 0) {
@@ -535,15 +466,6 @@ function createElIds() {
       SQUARE_ELS_IDS[square] = square + '-' + createId();
     }
   }
-
-  // spare pieces
-  var pieces = 'KQRBNP'.split('');
-  for (var i = 0; i < pieces.length; i++) {
-    var whitePiece = 'w' + pieces[i];
-    var blackPiece = 'b' + pieces[i];
-    SPARE_PIECE_ELS_IDS[whitePiece] = whitePiece + '-' + createId();
-    SPARE_PIECE_ELS_IDS[blackPiece] = blackPiece + '-' + createId();
-  }
 }
 
 //------------------------------------------------------------------------------
@@ -553,18 +475,8 @@ function createElIds() {
 function buildBoardContainer() {
   var html = '<div class="' + CSS.chessboard + '">';
 
-  if (cfg.sparePieces === true) {
-    html += '<div class="' + CSS.sparePieces + ' ' +
-      CSS.sparePiecesTop + '"></div>';
-  }
-
   html += '<div class="' + CSS.board + '"></div>';
 
-  if (cfg.sparePieces === true) {
-    html += '<div class="' + CSS.sparePieces + ' ' +
-      CSS.sparePiecesBottom + '"></div>';
-  }
-
   html += '</div>';
 
   return html;
@@ -667,62 +579,56 @@ function buildPieceImgSrc(piece) {
  * @param {!string=} id
  */
 function buildPiece(piece, hidden, id) {
-  var html = '<img src="' + buildPieceImgSrc(piece) + '" ';
+  let img = document.createElement('img');
+  img.src = buildPieceImgSrc(piece);
   if (id && typeof id === 'string') {
-    html += 'id="' + id + '" ';
+    img.setAttribute('id', id);
   }
-  html += 'alt="" ' +
-  'class="' + CSS.piece + '" ' +
-  'data-piece="' + piece + '" ' +
-  'style="width: ' + SQUARE_SIZE + 'px;' +
-  'height: ' + SQUARE_SIZE + 'px;';
+  img.setAttribute('alt', '');
+  img.classList.add(CSS.piece);
+  img.setAttribute('data-piece', piece);
+  img.style.width = SQUARE_SIZE + 'px';
+  img.style.height = SQUARE_SIZE + 'px';
   if (hidden === true) {
-    html += 'display:none;';
+    img.style.display = 'none';
   }
-  html += '" />';
-
-  return html;
-}
-
-function buildSparePieces(color) {
-  var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'];
-  if (color === 'black') {
-    pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'];
-  }
-
-  var html = '';
-  for (var i = 0; i < pieces.length; i++) {
-    html += buildPiece(pieces[i], false, SPARE_PIECE_ELS_IDS[pieces[i]]);
-  }
-
-  return html;
+  return img;
 }
 
 //------------------------------------------------------------------------------
 // Animations
 //------------------------------------------------------------------------------
 
+function offset(el) {  // From https://youmightnotneedjquery.com/.
+  let box = el.getBoundingClientRect();
+  let docElem = document.documentElement;
+  return {
+    top: box.top + window.pageYOffset - docElem.clientTop,
+    left: box.left + window.pageXOffset - docElem.clientLeft
+  };
+}
+
 function animateSquareToSquare(src, dest, piece, completeFn) {
   // get information about the source and destination squares
-  var srcSquareEl = $('#' + SQUARE_ELS_IDS[src]);
-  var srcSquarePosition = srcSquareEl.offset();
-  var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]);
-  var destSquarePosition = destSquareEl.offset();
+  var srcSquareEl = document.getElementById(SQUARE_ELS_IDS[src]);
+  var srcSquarePosition = offset(srcSquareEl);
+  var destSquareEl = document.getElementById(SQUARE_ELS_IDS[dest]);
+  var destSquarePosition = offset(destSquareEl);
 
   // create the animated piece and absolutely position it
   // over the source square
   var animatedPieceId = createId();
-  $('body').append(buildPiece(piece, true, animatedPieceId));
-  var animatedPieceEl = $('#' + animatedPieceId);
-  animatedPieceEl.css({
-    display: '',
-    position: 'absolute',
-    top: srcSquarePosition.top,
-    left: srcSquarePosition.left
-  });
-
-  // remove original piece from source square
-  srcSquareEl.find('.' + CSS.piece).remove();
+  document.body.append(buildPiece(piece, true, animatedPieceId));
+  var animatedPieceEl = document.getElementById(animatedPieceId);
+  animatedPieceEl.style.display = null;
+  animatedPieceEl.style.position = 'absolute';
+  animatedPieceEl.style.top = srcSquarePosition.top + 'px';
+  animatedPieceEl.style.left = srcSquarePosition.left + 'px';
+
+  // remove original piece(s) from source square
+  // TODO: multiple pieces should never really happen, but it will if we are moving
+  // while another animation still isn't done
+  srcSquareEl.querySelectorAll('.' + CSS.piece).forEach((piece) => piece.remove());
 
   // on complete
   var complete = function() {
@@ -739,65 +645,59 @@ function animateSquareToSquare(src, dest, piece, completeFn) {
   };
 
   // animate the piece to the destination square
-  var opts = {
-    duration: cfg.moveSpeed,
-    complete: complete
-  };
-  animatedPieceEl.animate(destSquarePosition, opts);
+  animatedPieceEl.addEventListener('transitionend', complete, {once: true});
+  requestAnimationFrame(() => {
+    animatedPieceEl.style.transitionProperty = 'top, left';
+    animatedPieceEl.style.transitionDuration = cfg.moveSpeed + 'ms';
+    animatedPieceEl.style.top = destSquarePosition.top + 'px';
+    animatedPieceEl.style.left = destSquarePosition.left + 'px';
+  });
 }
 
-function animateSparePieceToSquare(piece, dest, completeFn) {
-  var srcOffset = $('#' + SPARE_PIECE_ELS_IDS[piece]).offset();
-  var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]);
-  var destOffset = destSquareEl.offset();
-
-  // create the animate piece
-  var pieceId = createId();
-  $('body').append(buildPiece(piece, true, pieceId));
-  var animatedPieceEl = $('#' + pieceId);
-  animatedPieceEl.css({
-    display: '',
-    position: 'absolute',
-    left: srcOffset.left,
-    top: srcOffset.top
+function fadeIn(pieces, onFinish) {
+  pieces.forEach((piece) => {
+    piece.style.opacity = 0;
+    piece.style.display = null;
+    piece.addEventListener('transitionend', onFinish, {once: true});
   });
+  requestAnimationFrame(() => {
+    pieces.forEach((piece) => {
+      piece.style.transitionProperty = 'opacity';
+      piece.style.transitionDuration = cfg.appearSpeed + 'ms';
+      piece.style.opacity = 1;
+    });
+  });
+}
 
-  // on complete
-  var complete = function() {
-    // add the "real" piece to the destination square
-    destSquareEl.find('.' + CSS.piece).remove();
-    destSquareEl.append(buildPiece(piece));
-
-    // remove the animated piece
-    animatedPieceEl.remove();
-
-    // run complete function
-    if (typeof completeFn === 'function') {
-      completeFn();
-    }
-  };
-
-  // animate the piece to the destination square
-  var opts = {
-    duration: cfg.moveSpeed,
-    complete: complete
-  };
-  animatedPieceEl.animate(destOffset, opts);
+function fadeOut(pieces, onFinish) {
+  pieces.forEach((piece) => {
+    piece.style.opacity = 1;
+    piece.style.display = null;
+    piece.addEventListener('transitionend', onFinish, {once: true});
+  });
+  requestAnimationFrame(() => {
+    pieces.forEach((piece) => {
+      piece.style.transitionProperty = 'opacity';
+      piece.style.transitionDuration = cfg.trashSpeed + 'ms';
+      piece.style.opacity = 0;
+    });
+  });
 }
 
 // execute an array of animations
 function doAnimations(a, oldPos, newPos) {
-  ANIMATION_HAPPENING = true;
-
   var numFinished = 0;
-  function onFinish() {
+  function onFinish(e) {
+    if (e && e.target) {
+      e.target.transitionProperty = null;
+    }
+
     numFinished++;
 
     // exit if all the animations aren't finished
     if (numFinished !== a.length) return;
 
     drawPositionInstant();
-    ANIMATION_HAPPENING = false;
 
     // run their onMoveEnd function
     if (cfg.hasOwnProperty('onMoveEnd') === true &&
@@ -806,32 +706,42 @@ function doAnimations(a, oldPos, newPos) {
     }
   }
 
-  for (var i = 0; i < a.length; i++) {
-    // clear a piece
-    if (a[i].type === 'clear') {
-      $('#' + SQUARE_ELS_IDS[a[i].square] + ' .' + CSS.piece)
-        .fadeOut(cfg.trashSpeed, onFinish);
-    }
+  requestAnimationFrame(() => {  // Firefox workaround.
+    let fadeout_pieces = [];
+    let fadein_pieces = [];
 
-    // add a piece (no spare pieces)
-    if (a[i].type === 'add' && cfg.sparePieces !== true) {
-      $('#' + SQUARE_ELS_IDS[a[i].square])
-        .append(buildPiece(a[i].piece, true))
-        .find('.' + CSS.piece)
-        .fadeIn(cfg.appearSpeed, onFinish);
-    }
+    for (var i = 0; i < a.length; i++) {
+      // clear a piece
+      if (a[i].type === 'clear') {
+        document.getElementById(SQUARE_ELS_IDS[a[i].square]).querySelectorAll('.' + CSS.piece).forEach(
+          (piece) => fadeout_pieces.push(piece)
+        );
+      }
 
-    // add a piece from a spare piece
-    if (a[i].type === 'add' && cfg.sparePieces === true) {
-      animateSparePieceToSquare(a[i].piece, a[i].square, onFinish);
+      // add a piece
+      if (a[i].type === 'add') {
+        let square = document.getElementById(SQUARE_ELS_IDS[a[i].square]);
+        square.append(buildPiece(a[i].piece, true));
+        let piece = square.querySelector('.' + CSS.piece);
+        fadein_pieces.push(piece);
+      }
+
+      // move a piece
+      if (a[i].type === 'move') {
+        animateSquareToSquare(a[i].source, a[i].destination, a[i].piece,
+          onFinish);
+      }
     }
 
-    // move a piece
-    if (a[i].type === 'move') {
-      animateSquareToSquare(a[i].source, a[i].destination, a[i].piece,
-        onFinish);
+    // TODO: Batch moves as well, not just fade in/out.
+    // (We batch them because requestAnimationFrame seemingly costs real time.)
+    if (fadeout_pieces.length > 0) {
+      fadeOut(fadeout_pieces, onFinish);
     }
-  }
+    if (fadein_pieces.length > 0) {
+      fadeIn(fadein_pieces, onFinish);
+    }
+  });
 }
 
 // returns the distance between two squares
@@ -851,55 +761,26 @@ function squareDistance(s1, s2) {
   return yDelta;
 }
 
-// returns an array of closest squares from square
-function createRadius(square) {
-  var squares = [];
-
-  // calculate distance of all squares
-  for (var i = 0; i < 8; i++) {
-    for (var j = 0; j < 8; j++) {
-      var s = COLUMNS[i] + (j + 1);
-
-      // skip the square we're starting from
-      if (square === s) continue;
-
-      squares.push({
-        square: s,
-        distance: squareDistance(square, s)
-      });
-    }
-  }
-
-  // sort by distance
-  squares.sort(function(a, b) {
-    return a.distance - b.distance;
-  });
-
-  // just return the square code
-  var squares2 = [];
-  for (var i = 0; i < squares.length; i++) {
-    squares2.push(squares[i].square);
-  }
-
-  return squares2;
-}
-
 // returns the square of the closest instance of piece
 // returns false if no instance of piece is found in position
 function findClosestPiece(position, piece, square) {
-  // create array of closest squares from square
-  var closestSquares = createRadius(square);
-
-  // search through the position in order of distance for the piece
-  for (var i = 0; i < closestSquares.length; i++) {
-    var s = closestSquares[i];
+  let best_square = false;
+  let best_dist = 1e9;
+  for (var i = 0; i < COLUMNS.length; i++) {
+    for (var j = 1; j <= 8; j++) {
+      let other_square = COLUMNS[i] + j;
 
-    if (position.hasOwnProperty(s) === true && position[s] === piece) {
-      return s;
+      if (position[other_square] === piece && square != other_square) {
+        let dist = squareDistance(square, other_square);
+        if (dist < best_dist) {
+          best_square = other_square;
+          best_dist = dist;
+        }
+      }
     }
   }
 
-  return false;
+  return best_square;
 }
 
 // calculate an array of animations that need to happen in order to get
@@ -980,30 +861,19 @@ function calculateAnimations(pos1, pos2) {
 
 function drawPositionInstant() {
   // clear the board
-  boardEl.find('.' + CSS.piece).remove();
+  boardEl.querySelectorAll('.' + CSS.piece).forEach((piece) => piece.remove());
 
   // add the pieces
   for (var i in CURRENT_POSITION) {
     if (CURRENT_POSITION.hasOwnProperty(i) !== true) continue;
 
-    $('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i]));
+    document.getElementById(SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i]));
   }
 }
 
 function drawBoard() {
-  boardEl.html(buildBoard(CURRENT_ORIENTATION));
+  boardEl.innerHTML = buildBoard(CURRENT_ORIENTATION);
   drawPositionInstant();
-
-  if (cfg.sparePieces === true) {
-    if (CURRENT_ORIENTATION === 'white') {
-      sparePiecesTopEl.html(buildSparePieces('black'));
-      sparePiecesBottomEl.html(buildSparePieces('white'));
-    }
-    else {
-      sparePiecesTopEl.html(buildSparePieces('white'));
-      sparePiecesBottomEl.html(buildSparePieces('black'));
-    }
-  }
 }
 
 // given a position and a set of moves, return a new position
@@ -1065,28 +935,24 @@ function captureSquareOffsets() {
   for (var i in SQUARE_ELS_IDS) {
     if (SQUARE_ELS_IDS.hasOwnProperty(i) !== true) continue;
 
-    SQUARE_ELS_OFFSETS[i] = $('#' + SQUARE_ELS_IDS[i]).offset();
+    SQUARE_ELS_OFFSETS[i] = offset(document.getElementById(SQUARE_ELS_IDS[i]));
   }
 }
 
 function removeSquareHighlights() {
-  boardEl.find('.' + CSS.square)
-    .removeClass(CSS.highlight1 + ' ' + CSS.highlight2);
+  boardEl.querySelectorAll('.' + CSS.square).forEach((piece) => {
+    piece.classList.remove(CSS.highlight1);
+    piece.classList.remove(CSS.highlight2);
+  });
 }
 
 function snapbackDraggedPiece() {
-  // there is no "snapback" for spare pieces
-  if (DRAGGED_PIECE_SOURCE === 'spare') {
-    trashDraggedPiece();
-    return;
-  }
-
   removeSquareHighlights();
 
   // animation complete
   function complete() {
     drawPositionInstant();
-    draggedPieceEl.css('display', 'none');
+    draggedPieceEl.style.display = 'none';
 
     // run their onSnapbackEnd function
     if (cfg.hasOwnProperty('onSnapbackEnd') === true &&
@@ -1098,14 +964,16 @@ function snapbackDraggedPiece() {
 
   // get source square position
   var sourceSquarePosition =
-    $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_SOURCE]).offset();
+    offset(document.getElementById(SQUARE_ELS_IDS[DRAGGED_PIECE_SOURCE]));
 
   // animate the piece to the target square
-  var opts = {
-    duration: cfg.snapbackSpeed,
-    complete: complete
-  };
-  draggedPieceEl.animate(sourceSquarePosition, opts);
+  draggedPieceEl.addEventListener('transitionend', complete, {once: true});
+  requestAnimationFrame(() => {
+    draggedPieceEl.style.transitionProperty = 'top, left';
+    draggedPieceEl.style.transitionDuration = cfg.snapbackSpeed + 'ms';
+    draggedPieceEl.style.top = sourceSquarePosition.top + 'px';
+    draggedPieceEl.style.left = sourceSquarePosition.left + 'px';
+  });
 
   // set state
   DRAGGING_A_PIECE = false;
@@ -1123,7 +991,8 @@ function trashDraggedPiece() {
   drawPositionInstant();
 
   // hide the dragged piece
-  draggedPieceEl.fadeOut(cfg.trashSpeed);
+  // FIXME: support this for non-jquery
+  //$(draggedPieceEl).fadeOut(cfg.trashSpeed);
 
   // set state
   DRAGGING_A_PIECE = false;
@@ -1139,26 +1008,30 @@ function dropDraggedPieceOnSquare(square) {
   setCurrentPosition(newPosition);
 
   // get target square information
-  var targetSquarePosition = $('#' + SQUARE_ELS_IDS[square]).offset();
+  var targetSquarePosition = offset(document.getElementById(SQUARE_ELS_IDS[square]));
 
   // animation complete
   var complete = function() {
     drawPositionInstant();
-    draggedPieceEl.css('display', 'none');
+    draggedPieceEl.style.display = 'none';
 
     // execute their onSnapEnd function
     if (cfg.hasOwnProperty('onSnapEnd') === true &&
       typeof cfg.onSnapEnd === 'function') {
-      cfg.onSnapEnd(DRAGGED_PIECE_SOURCE, square, DRAGGED_PIECE);
+      requestAnimationFrame(() => {  // HACK: so that we don't add event handlers from the callback...
+        cfg.onSnapEnd(DRAGGED_PIECE_SOURCE, square, DRAGGED_PIECE);
+      });
     }
   };
 
   // snap the piece to the target square
-  var opts = {
-    duration: cfg.snapSpeed,
-    complete: complete
-  };
-  draggedPieceEl.animate(targetSquarePosition, opts);
+  draggedPieceEl.addEventListener('transitionend', complete, {once: true});
+  requestAnimationFrame(() => {
+    draggedPieceEl.style.transitionProperty = 'top, left';
+    draggedPieceEl.style.transitionDuration = cfg.snapSpeed + 'ms';
+    draggedPieceEl.style.top = targetSquarePosition.top + 'px';
+    draggedPieceEl.style.left = targetSquarePosition.left + 'px';
+  });
 
   // set state
   DRAGGING_A_PIECE = false;
@@ -1177,40 +1050,28 @@ function beginDraggingPiece(source, piece, x, y) {
   DRAGGING_A_PIECE = true;
   DRAGGED_PIECE = piece;
   DRAGGED_PIECE_SOURCE = source;
-
-  // if the piece came from spare pieces, location is offboard
-  if (source === 'spare') {
-    DRAGGED_PIECE_LOCATION = 'offboard';
-  }
-  else {
-    DRAGGED_PIECE_LOCATION = source;
-  }
+  DRAGGED_PIECE_LOCATION = source;
 
   // capture the x, y coords of all squares in memory
   captureSquareOffsets();
 
   // create the dragged piece
-  draggedPieceEl.attr('src', buildPieceImgSrc(piece))
-    .css({
-      display: '',
-      position: 'absolute',
-      left: x - (SQUARE_SIZE / 2),
-      top: y - (SQUARE_SIZE / 2)
-    });
-
-  if (source !== 'spare') {
-    // highlight the source square and hide the piece
-    $('#' + SQUARE_ELS_IDS[source]).addClass(CSS.highlight1)
-      .find('.' + CSS.piece).css('display', 'none');
-  }
+  draggedPieceEl.setAttribute('src', buildPieceImgSrc(piece));
+  draggedPieceEl.style.display = null;
+  draggedPieceEl.style.position = 'absolute';
+  draggedPieceEl.style.left = (x - (SQUARE_SIZE / 2)) + 'px';
+  draggedPieceEl.style.top = (y - (SQUARE_SIZE / 2)) + 'px';
+
+  // highlight the source square and hide the piece
+  let square = document.getElementById(SQUARE_ELS_IDS[source]);
+  square.classList.add(CSS.highlight1);
+  square.querySelector('.' + CSS.piece).style.display = 'none';
 }
 
 function updateDraggedPiece(x, y) {
   // put the dragged piece over the mouse cursor
-  draggedPieceEl.css({
-    left: x - (SQUARE_SIZE / 2),
-    top: y - (SQUARE_SIZE / 2)
-  });
+  draggedPieceEl.style.left = (x - (SQUARE_SIZE / 2)) + 'px';
+  draggedPieceEl.style.top = (y - (SQUARE_SIZE / 2)) + 'px';
 
   // get location
   var location = isXYOnSquare(x, y);
@@ -1220,13 +1081,13 @@ function updateDraggedPiece(x, y) {
 
   // remove highlight from previous square
   if (validSquare(DRAGGED_PIECE_LOCATION) === true) {
-    $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_LOCATION])
-      .removeClass(CSS.highlight2);
+    document.getElementById(SQUARE_ELS_IDS[DRAGGED_PIECE_LOCATION])
+      .classList.remove(CSS.highlight2);
   }
 
   // add highlight to new square
   if (validSquare(location) === true) {
-    $('#' + SQUARE_ELS_IDS[location]).addClass(CSS.highlight2);
+    document.getElementById(SQUARE_ELS_IDS[location]).classList.add(CSS.highlight2);
   }
 
   // run onDragMove
@@ -1255,16 +1116,6 @@ function stopDraggedPiece(location) {
     typeof cfg.onDrop === 'function') {
     var newPosition = deepCopy(CURRENT_POSITION);
 
-    // source piece is a spare piece and position is off the board
-    //if (DRAGGED_PIECE_SOURCE === 'spare' && location === 'offboard') {...}
-    // position has not changed; do nothing
-
-    // source piece is a spare piece and position is on the board
-    if (DRAGGED_PIECE_SOURCE === 'spare' && validSquare(location) === true) {
-      // add the piece to the board
-      newPosition[location] = DRAGGED_PIECE;
-    }
-
     // source piece was on the board and position is off the board
     if (validSquare(DRAGGED_PIECE_SOURCE) === true && location === 'offboard') {
       // remove the piece from the board
@@ -1323,11 +1174,8 @@ widget.config = function(arg1, arg2) {
 // remove the widget from the page
 widget.destroy = function() {
   // remove markup
-  containerEl.html('');
+  containerEl.innerHTML = '';
   draggedPieceEl.remove();
-
-  // remove event handlers
-  containerEl.unbind();
 };
 
 // shorthand method to get the current FEN
@@ -1462,18 +1310,12 @@ widget.resize = function() {
   SQUARE_SIZE = calculateSquareSize();
 
   // set board width
-  boardEl.css('width', (SQUARE_SIZE * 8) + 'px');
+  boardEl.style.width = (SQUARE_SIZE * 8) + 'px';
 
   // set drag piece size
-  draggedPieceEl.css({
-    height: SQUARE_SIZE,
-    width: SQUARE_SIZE
-  });
-
-  // spare pieces
-  if (cfg.sparePieces === true) {
-    containerEl.find('.' + CSS.sparePieces)
-      .css('paddingLeft', (SQUARE_SIZE + BOARD_BORDER_SIZE) + 'px');
+  if (draggedPieceEl !== null) {
+    draggedPieceEl.style.height = SQUARE_SIZE + 'px';
+    draggedPieceEl.style.width = SQUARE_SIZE + 'px';
   }
 
   // redraw the board
@@ -1493,21 +1335,16 @@ function isTouchDevice() {
   return ('ontouchstart' in document.documentElement);
 }
 
-// reference: http://www.quirksmode.org/js/detect.html
-function isMSIE() {
-  return (navigator && navigator.userAgent &&
-      navigator.userAgent.search(/MSIE/) !== -1);
-}
-
-function stopDefault(e) {
-  e.preventDefault();
-}
-
 function mousedownSquare(e) {
+  let target = e.target.closest('.' + CSS.square);
+  if (!target) {
+    return;
+  }
+
   // do nothing if we're not draggable
   if (cfg.draggable !== true) return;
 
-  var square = $(this).attr('data-square');
+  var square = target.getAttribute('data-square');
 
   // no piece on this square
   if (validSquare(square) !== true ||
@@ -1519,10 +1356,15 @@ function mousedownSquare(e) {
 }
 
 function touchstartSquare(e) {
+  let target = e.target.closest('.' + CSS.square);
+  if (!target) {
+    return;
+  }
+
   // do nothing if we're not draggable
   if (cfg.draggable !== true) return;
 
-  var square = $(this).attr('data-square');
+  var square = target.getAttribute('data-square');
 
   // no piece on this square
   if (validSquare(square) !== true ||
@@ -1530,31 +1372,10 @@ function touchstartSquare(e) {
     return;
   }
 
-  e = e.originalEvent;
   beginDraggingPiece(square, CURRENT_POSITION[square],
     e.changedTouches[0].pageX, e.changedTouches[0].pageY);
 }
 
-function mousedownSparePiece(e) {
-  // do nothing if sparePieces is not enabled
-  if (cfg.sparePieces !== true) return;
-
-  var piece = $(this).attr('data-piece');
-
-  beginDraggingPiece('spare', piece, e.pageX, e.pageY);
-}
-
-function touchstartSparePiece(e) {
-  // do nothing if sparePieces is not enabled
-  if (cfg.sparePieces !== true) return;
-
-  var piece = $(this).attr('data-piece');
-
-  e = e.originalEvent;
-  beginDraggingPiece('spare', piece,
-    e.changedTouches[0].pageX, e.changedTouches[0].pageY);
-}
-
 function mousemoveWindow(e) {
   // do nothing if we are not dragging a piece
   if (DRAGGING_A_PIECE !== true) return;
@@ -1569,8 +1390,8 @@ function touchmoveWindow(e) {
   // prevent screen from scrolling
   e.preventDefault();
 
-  updateDraggedPiece(e.originalEvent.changedTouches[0].pageX,
-    e.originalEvent.changedTouches[0].pageY);
+  updateDraggedPiece(e.changedTouches[0].pageX,
+    e.changedTouches[0].pageY);
 }
 
 function mouseupWindow(e) {
@@ -1588,13 +1409,18 @@ function touchendWindow(e) {
   if (DRAGGING_A_PIECE !== true) return;
 
   // get the location
-  var location = isXYOnSquare(e.originalEvent.changedTouches[0].pageX,
-    e.originalEvent.changedTouches[0].pageY);
+  var location = isXYOnSquare(e.changedTouches[0].pageX,
+    e.changedTouches[0].pageY);
 
   stopDraggedPiece(location);
 }
 
 function mouseenterSquare(e) {
+  let target = e.target.closest('.' + CSS.square);
+  if (!target) {
+    return;
+  }
+
   // do not fire this event if we are dragging a piece
   // NOTE: this should never happen, but it's a safeguard
   if (DRAGGING_A_PIECE !== false) return;
@@ -1603,7 +1429,7 @@ function mouseenterSquare(e) {
     typeof cfg.onMouseoverSquare !== 'function') return;
 
   // get the square
-  var square = $(e.currentTarget).attr('data-square');
+  var square = target.getAttribute('data-square');
 
   // NOTE: this should never happen; defensive
   if (validSquare(square) !== true) return;
@@ -1620,6 +1446,11 @@ function mouseenterSquare(e) {
 }
 
 function mouseleaveSquare(e) {
+  let target = e.target.closest('.' + CSS.square);
+  if (!target) {
+    return;
+  }
+
   // do not fire this event if we are dragging a piece
   // NOTE: this should never happen, but it's a safeguard
   if (DRAGGING_A_PIECE !== false) return;
@@ -1628,7 +1459,7 @@ function mouseleaveSquare(e) {
     typeof cfg.onMouseoutSquare !== 'function') return;
 
   // get the square
-  var square = $(e.currentTarget).attr('data-square');
+  var square = target.getAttribute('data-square');
 
   // NOTE: this should never happen; defensive
   if (validSquare(square) !== true) return;
@@ -1650,58 +1481,44 @@ function mouseleaveSquare(e) {
 
 function addEvents() {
   // prevent browser "image drag"
-  $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault);
+  let stopDefault = (e) => {
+    if (e.target.matches('.' + CSS.piece)) {
+      e.preventDefault();
+    }
+  };
+  document.body.addEventListener('mousedown', stopDefault);
+  document.body.addEventListener('mousemove', stopDefault);
 
   // mouse drag pieces
-  boardEl.on('mousedown', '.' + CSS.square, mousedownSquare);
-  containerEl.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece,
-    mousedownSparePiece);
+  boardEl.addEventListener('mousedown', mousedownSquare);
 
   // mouse enter / leave square
-  boardEl.on('mouseenter', '.' + CSS.square, mouseenterSquare);
-  boardEl.on('mouseleave', '.' + CSS.square, mouseleaveSquare);
-
-  // IE doesn't like the events on the window object, but other browsers
-  // perform better that way
-  if (isMSIE() === true) {
-    // IE-specific prevent browser "image drag"
-    document.ondragstart = function() { return false; };
+  boardEl.addEventListener('mouseenter', mouseenterSquare);
+  boardEl.addEventListener('mouseleave', mouseleaveSquare);
 
-    $('body').on('mousemove', mousemoveWindow);
-    $('body').on('mouseup', mouseupWindow);
-  }
-  else {
-    $(window).on('mousemove', mousemoveWindow);
-    $(window).on('mouseup', mouseupWindow);
-  }
+  window.addEventListener('mousemove', mousemoveWindow);
+  window.addEventListener('mouseup', mouseupWindow);
 
   // touch drag pieces
   if (isTouchDevice() === true) {
-    boardEl.on('touchstart', '.' + CSS.square, touchstartSquare);
-    containerEl.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece,
-      touchstartSparePiece);
-    $(window).on('touchmove', touchmoveWindow);
-    $(window).on('touchend', touchendWindow);
+    boardEl.addEventListener('touchstart', touchstartSquare);
+    window.addEventListener('touchmove', touchmoveWindow);
+    window.addEventListener('touchend', touchendWindow);
   }
 }
 
 function initDom() {
   // build board and save it in memory
-  containerEl.html(buildBoardContainer());
-  boardEl = containerEl.find('.' + CSS.board);
-
-  if (cfg.sparePieces === true) {
-    sparePiecesTopEl = containerEl.find('.' + CSS.sparePiecesTop);
-    sparePiecesBottomEl = containerEl.find('.' + CSS.sparePiecesBottom);
-  }
+  containerEl.innerHTML = buildBoardContainer();
+  boardEl = containerEl.querySelector('.' + CSS.board);
 
   // create the drag piece
   var draggedPieceId = createId();
-  $('body').append(buildPiece('wP', true, draggedPieceId));
-  draggedPieceEl = $('#' + draggedPieceId);
+  document.body.append(buildPiece('wP', true, draggedPieceId));
+  draggedPieceEl = document.getElementById(draggedPieceId);
 
   // get the border size
-  BOARD_BORDER_SIZE = parseInt(boardEl.css('borderLeftWidth'), 10);
+  BOARD_BORDER_SIZE = parseInt(boardEl.style.borderLeftWidth, 10);
 
   // set the size and draw the board
   widget.resize();