JSFiddle - React, Tailwind, and code Playground

by Craig Martin

HTML

<img id="gridimg" src= "https://images.pexels.com/photos/66997/pexels-photo-66997.jpeg"/>

CSS

.splitting-image {
  background-size: 0% 0%;
  position: relative;
  overflow: hidden;
}

.splitting-image img {
  display: block;
}

@supports (--row-size: calc( 100% / 2 )) {
  .splitting-image {
    background-size: cover;
    visibility: hidden;
    --row-size: calc(100% / var(--row-total));
    --col-size: calc(100% / var(--col-total));  

    --center-x: calc((var(--col-total) - 1) / 2);
    --center-y: calc((var(--row-total) - 1) / 2);
  }

  .split-cell {
    background: inherit;
    position: absolute;
    overflow: hidden;
    top: calc(var(--row-size) * var(--row-index));
    left: calc(var(--col-size) * var(--col-index));
    width: calc(0.5px + var(--col-size));
    height: calc(0.5px + var(--row-size));
    
    /* Offset from center, positive & negative */
    --offset-x: calc(var(--col-index) - var(--center-x));
    --offset-y: calc(var(--row-index) - var(--center-y));

    /* Absolute distance from center, only positive */
    --distance-x: calc(
       (var(--offset-x) * var(--offset-x)) / var(--center-x)
    );

    /* Absolute distance from center, only positive */
    --distance-y: calc(
       (var(--offset-y) * var(--offset-y)) / var(--center-y)
    );
  }

  .split-cell__inner {
    position: absolute;
    visibility: visible;
    background: inherit;
    width: calc(100% * var(--col-total));
    height: calc(100% * var(--row-total));
    transform: translate(
      calc(-1 * var(--col-size) * var(--col-index)),
      calc(-1 * var(--row-size) * var(--row-index))
    );
  }
}

JavaScript

/**
 * # SplittingImage
 *
 * @param {*} els
 */
function SplittingImage(els, opts) {
  opts = opts || {};

  return $(els).map(function(el, i) {
    if (!el._splitImg) {
      el._splitImg = split(el, opts);
      if (el.classList) {
        el.classList.add("splitting-image");
      }
    }
    return el._splitImg;
  });
}

/**
 * # SplittingImage.$
 * Convert selector or NodeList to array for easier manipulation.
 *
 * @param {*} els - Elements or selector
 */
function $(els) {
  return Array.prototype.slice.call(
    els.nodeName
      ? [els]
      : els[0].nodeName ? els : document.querySelectorAll(els),
    0
  );
}
SplittingImage.$ = $;

/**
 * # SplittingImage.split
 * Split an element with the specified number of rows & cols
 * @param {Node} el - Element to split
 * @param {String} key -
 * @param {String|RegEx} splitBy - string or regex to split the innerText by
 * @param {Boolean} space - Add a space to each split if index is greater than 0. Mainly for `Splitting.words`
 */
function split(el, opts) {
  var img =
      opts.image || (el.dataset && el.dataset.image) || el.currentSrc || el.src,
    rows = opts.rows || (el.dataset && el.dataset.rows) || 1,
    cols = opts.cols || (el.dataset && el.dataset.cols) || 1,
    row = 0,
    col = 0,
    cells = [],
    parent = el.parentNode,
    cell,
    inner,
    temp;

  // Use fragment to prevent unnecessary DOM thrashing.
  var fragment = document.createDocumentFragment();

  for (; row < rows; row++) {
    for (col = 0; col < cols; col++) {
      // Create a span
      cell = document.createElement("span");
      cell.className = "split-cell";
      cell.style.setProperty("--row-index", row);
      cell.style.setProperty("--col-index", col);
      cell.style.setProperty("--cell-index", cells.length);
      cell.setAttribute("data-row", row);
      cell.setAttribute("data-col", col);

      inner = document.createElement("span");
      inner.className = "split-cell__inner";
     ...