cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - Activehtmlicon-personicon-teamoctocatspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <h1>Responsively crop copy, restore onclick via typed animation</h1>

<p>Responsively crop content copy down to a user-defined number of lines.<br>Click to restore content via a typed animation.</p>
<p>Vanilla JavaScript running at 60fps.<br>1K approx minified &amp; gzipped.</p>

<p>UPDATED: Please take a loook at the smooth drop-down version: <a href="https://codepen.io/2kool2/pen/PWmzMa">Responsively crop copy, restore onclick via sliding drop-down animation</a></p>

<p>GitHub repo: <a href="https://github.com/2kool2/crop-copy-restore" target=_blank title="[new window]">crop-copy-restore</a></p>

<h2>Single line crop</h2>
<div class=txt data-cropCopyRestore>My wife Maria, loves hot spicy food. I found this out on our first date. She said “I really fancy a curry”. Well, I was keen to impress so I said, “great, that’s my favourite too ”But it isn’t, never has been. We’ve been married 15 years and I still haven’t let on. Don’t know why. But whenever I cook her favourite chicken gujarati extra spicy, with green beans and fresh ginger, I do what any self-respecting chef would do. I cheat. I put a king size spoonful of natural yoghurt in mine. And I keep schtum.</div>

<h2>Three lines</h2>
<div class=txt data-cropCopyRestore=3>Sometimes, it's the undemandingly easy, everyday, recipes that deliver the most joy. For 'Iain' and his dad, from our Food Love Story, it's croque monsieur – they first had it on a joint trip to France, and since then it's become their favourite weekend lunch. Iain's made a few changes to it along the way (bonjour, wafer thin roast turkey) – but for him and his dad, it's most definitely 'proper'.</div>

<h2>Two lines</h2>
<div class=txt data-cropCopyRestore=2>Any parent will probably be able to relate to 'Sunita' in our Food Love Story. Kids can be champion fussy eaters, especially when it comes to vegetables. Here's a genius solution – meatballs packed with under-the-radar veg. Sunita's kids love them, and are none the wiser.</div>

<h2>Status</h2>
<p>Considered too jarring visually.<br>Project to be redeveloped with smooth text expansion.</p>

<p class=smaller><a target=_blank title="[new window]" href="https://codepen.io/2kool2/pens/public/?grid_type=list#">Pens by Mike Foskett</a> &mdash; <a target=_blank title="[new window]" href="https://websemantics.uk/">webSemantics</a></p>
            
          
!
            
              .txt {
  position:relative;  
  margin: 2rem;
}
[data-cropCopyRestore][role="button"] {
  display: block;
  padding: .75rem 1rem;
  background-color: #000;
  transition: all .3s ease-out;
  -ms-touch-action: manipulation;
  touch-action: manipulation;
}
[data-cropCopyRestore][role="button"]:focus {
  outline: 0 dotted #fff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .5);
}
[data-cropCopyRestore][role="button"]:hover {
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .5);
  cursor: pointer;
}

/* Required */
.-clone {
  visibility: hidden;
  opacity: 0;
  z-index: -1;
}

            
          
!
            
              // Crop copy responsively, to user-defined number of lines, then restore onclick - v1.0 - 08/01/2017 - M.J.Foskett - https://websemantics.uk/
var cropCopyRestore = function (window, document) {

  "use strict";

  var dataAttr = "data-cropCopyRestore";
  var buttonId = "cropCopyRestore_btn-";
  var ellipsis = "…"; // "\u2026"
  var clonedClass = "-clone";

  // https://john-dugan.com/javascript-debounce/
  var debounce=function(e,t,n){var a;return function(){var r=this,i=arguments,o=function(){a=null,n||e.apply(r,i)},s=n&&!a;clearTimeout(a),a=setTimeout(o,t||200),s&&e.apply(r,i)}};


// String cropping functions

  function _removeLastOccur(str, removeStr) {
    return str.substring(0, str.lastIndexOf(removeStr));
  }

  function _removeTrailingPunct(str) {
    return str.replace(/[ .,!?:;"“‘'\-]+$/, "");
  }

// Display and animation functions

  // https://hacks.mozilla.org/2011/08/animating-with-javascript-from-setinterval-to-requestanimationframe/
  function _animLoop(f,g){function b(c){if(!1!==d){window.requestAnimationFrame(b,g);var e=c-a;160>e&&(d=f(e));a=c}}var d,a=+new Date;b(a)}
  // Usage:
  //animLoop(function(deltaT) {
  //  elem.style.left = (left += 10 * deltaT / 16) + "px";
  //  if (left > 400) {
  //    return false;
  //  }
  // optional 2nd arg: elem containing the animation
  //}, animationFunction);

  function _display(obj, str) {
    obj.textContent = str;
  }

  function _displayAppend(obj, str) {
    _display(obj, obj.textContent + str);
  }

  function _displayCroppedText(obj) {
    _display(obj, obj.croppedTxt + ellipsis)
  }

  function _resetAttr(obj, bool) {
    obj.setAttribute("aria-expanded", bool);
    obj.transitioning = false;
  }

  function _addRemainerText(obj) {
    var textArr = obj.remainerTxt.split(" ");
    var i = 0;

    window.requestAnimationFrame(function() {

      // remove ellipsis
      _display(obj, obj.croppedTxt);

      _animLoop(function() {
        _displayAppend(obj, textArr[i] + " ");
        if (i >= textArr.length - 1) {
          _resetAttr(obj, true);
          return false;
        }
        i += 1;
      });
    });
  }

  function _removeRemainerText(obj) {
    var textArr = obj.remainerTxt.split(" ");
    var i = textArr.length;

    _animLoop(function() {
      i -= 1;
      _display(obj, _removeLastOccur(obj.textContent, " " + textArr[i]));
      if (i <= 0) {
        _displayCroppedText(obj);
        _resetAttr(obj, false);
        return false;
      }
    });
  }

// Set copy

  function _getRemainerText(obj) {
    return obj.fullTxt.replace(obj.croppedTxt, "");
  }

  function _createClone(obj, str) {
    // create an invisible clone (used to get an objects height)
    var clone = obj.cloneNode(true);
    clone.setAttribute("class", obj.className + " " + clonedClass);
    clone.textContent = str;
    obj.parentNode.insertBefore(clone, obj.nextSibling);
    clone.initialHeight = clone.clientHeight;
    return clone;
  }

  function _getCroppedText(obj) {
    var txtArr = obj.fullTxt.split(" ");
    var i = 0;
    var lines = 1;
    var clone = _createClone(obj, txtArr[i] + " ");

    for (i = 1; i < txtArr.length; i++) {
      _displayAppend(clone, txtArr[i] + ellipsis);
      if (clone.clientHeight !== clone.initialHeight) {

        if (lines + "" === obj.noOfLines) {
          _display(clone, _removeLastOccur(clone.textContent, txtArr[i] + ellipsis));
          break;
        }
        lines++;
        clone.initialHeight = clone.clientHeight;
      }

      // Bit of an assumption
      _display(clone, clone.textContent.replace(txtArr[i] + ellipsis, txtArr[i] + " "));

    }

    _display(clone, _removeTrailingPunct(clone.textContent));
    obj.parentNode.removeChild(clone);
    return clone.textContent;
  }

// Handle events

  function _clicked(event) {
    var obj = event.target;
    if (!obj.transitioning) {
      obj.transitioning = true;
      if (obj.getAttribute("aria-expanded") === "true") {
        _removeRemainerText(obj);
      } else {
        _addRemainerText(obj);
      }
    }
    event.preventDefault();
  }

  function _keyPressed(event) {
    // Enter or space key
    if (event.which === 13 || event.which === 32) {
      _clicked(event);
    }
  }

  function _addEvents(obj) {
    obj.addEventListener("click", _clicked, false);
    obj.addEventListener("keydown", _keyPressed, false);
  }

  function _removeEvents(obj) {
    obj.removeEventListener("click", _clicked);
    obj.removeEventListener("keydown", _keyPressed);
  }

// Initialisation

  function _initialiseAttributes(obj, i) {
    var str = obj.getAttribute(dataAttr);
    obj.noOfLines = (/^([1-9]\d*)$/.test(str)) ? str : "1"; // 1 - 9 only
    obj.fullTxt = obj.textContent;
    obj.setAttribute("id", obj.id || buttonId + i);
    obj.setAttribute("role", "button");
    obj.setAttribute("tabindex", "0");
    obj.setAttribute("aria-controls", obj.id);
  }

  function start() {

    var objs = document.querySelectorAll("[" + dataAttr + "]");
    var i = objs.length;
    var obj;

    while (i--) {

      obj = objs[i];

      // In case it's a resize call rather than initialisation
      if (obj.fullTxt) {
        _removeEvents(obj);
      } else {
        _initialiseAttributes(obj, i);
      }

      // Reset, or initialise, common attributes
      _resetAttr(obj, false);
      obj.croppedTxt = _getCroppedText(obj);
      obj.remainerTxt = _getRemainerText(obj);

      _displayCroppedText(obj);
      _addEvents(obj);
    }
  }

  start();
  window.addEventListener("resize", debounce(start, 100, false), false);

}(window, document);

window.addEventListener("load", cropCopyRestore, false);

            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console