1

I have a really strange behavior when i using str.replace. I have a function that imitate typing, and everything works great, but I added option skipTags and if is set to true than I use this:

str = str.replace(/(<([^>]+)>)/ig,"");

but after that animation prints only first line. After typing is end, then all text appears. If skipTags is set to false everything is OK.

/*
 *
 *
 *
 * Copyright (c) 2015 Michał Baryś
 * Licensed under the MIT license.
 */
(function($) {
  $.fn.jQueryTypingAnimationPlugin = function(options) {

    var settings = $.extend({
      // These are the defaults.
      startDelay: 0,
      speed: 50,
      skipTags: false,
      onStart: null,
      onTyping: null,
      onComplete: null
    }, options);

    return this.each(function(idx) {
      // Do something to each selected element.


      var $elem = $(this);

      if ($elem.prop("tagName") !== 'INPUT') {

        var sizes = [];
        console.log($elem.css('box-sizing'));
        switch ($elem.css('box-sizing')) {
          // case 'padding-box':
          //  sizes = [ $(this).innerWidth(), $(this).innerHeight() ];
          //  console.log('padding-box');
          // break;

          // case 'border-box':
          //  sizes = [ $(this).outerWidth(), $(this).outerHeight() ];
          //  console.log('border-box');
          // break;

          default: sizes = [$elem.width(), $elem.height()];
          console.log('content-box');

        }



        $elem.width(sizes[0]);
        $elem.height(sizes[1]);
      }
      $elem.addClass('typing-animation');



      var str,
        i = 0,
        isTag;

      if ($elem.prop("tagName") === 'INPUT') {
        str = $elem.val();
        $elem.val('');
        $elem.focus();
      } else {
        str = $elem.html();
        $elem.html('');
      }


      if (settings.skipTags === true) {
        str = str.replace(/(<([^>]+)>)/ig, "");
      }

      console.log('str', str);


      function type() {

        if (i === 0 && settings.onStart) {
          settings.onStart();
        }

        var text = str.slice(0, ++i);


        if (settings.onTyping) {
          settings.onTyping(i - 1, text.slice(-1), text);
        }
        if ($elem.prop("tagName") === 'INPUT') {
          $elem.val(text);
        } else {
          $elem.html(text);
        }

        var char = text.slice(-1);
        if (char === '<') {
          isTag = true;
        }
        if (char === '>') {
          isTag = false;
        }

        if (isTag) {
          return type();
        }

        if (text === str) {
          $elem.removeAttr('style');
          if (settings.onComplete) {
            settings.onComplete();
          }
          return;
        }

        setTimeout(type, settings.speed);

      };

      setTimeout(type, settings.startDelay);




    });
  };
}(jQuery));


// init plugin
$('p').jQueryTypingAnimationPlugin({
  startDelay: 0,
  speed: 100,
  skipTags: true,
  onStart: function() {
    console.log('start typing');
  },
  onTyping: function(idx, char, text) {
    //console.log( 'on index ' + idx + ' is letter "' + char + '", current text: "'+text+'"' ); 
  },
  onComplete: function() {
    console.log('Typing complete');
  }
});
.typing-animation {
  -webkit-transform: translateZ(0);
  -moz-transform: translateZ(0);
  -ms-transform: translateZ(0);
  -o-transform: translateZ(0);
  transform: translateZ(0);
}
.typing-animation:after {
  content: "|";
  -moz-animation: blink 1.1s steps(5, start) infinite;
  -webkit-animation: blink 1.1s steps(5, start) infinite;
  animation: blink 1.1s steps(5, start) infinite;
  font-weight: 100;
  /*width: 10px;
    min-height: 45px;
    height: 100%;
     background: #000;
     display: inline-block;*/
}
@-moz-keyframes blink {
  to {
    visibility: hidden;
  }
}
@-webkit-keyframes blink {
  to {
    visibility: hidden;
  }
}
@keyframes blink {
  to {
    visibility: hidden;
  }
}
p {
  background: yellow;
  width: 300px;
  margin: 20px auto;
  padding: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p><em>Lorem</em> ipsum dolor <a href="#" target="_blank">sit amet</a>, consectetur adipisicing elit. Fuga, similique necessitatibus tenetur beatae suscipit, voluptatum repellat doloremque saepe, in dicta numquam. Ad impedit voluptatum ipsa id, consequuntur
  hic eaque dignissimos minima laboriosam, enim fuga praesentium commodi nisi laudantium non dolores voluptatibus veniam numquam eligendi fugit. Et, voluptas. Cum minus, cupiditate.</p>
  • 1
    Have a look at [this answer](http://stackoverflow.com/a/11665782/1048572) for doing such an animation without any regex html manipulation and not loosing tags. – Bergi Jul 28 '15 at 12:24

2 Answers2

1

Looks like a weird browser bug. I can't see anything wrong with your code.

In Chrome, it appears that a workaround is to add display: inline-block; to the style applied to the blinking cursor:

.typing-animation:after {
  content: "|";
  -moz-animation: blink 1.1s steps(5, start) infinite;
  -webkit-animation: blink 1.1s steps(5, start) infinite;
  animation: blink 1.1s steps(5, start) infinite;
  font-weight: 100;
  display: inline-block;  /** workaround for Chrome bug **/
}

I haven't tested this in any other browsers, however.

/*
 *
 *
 *
 * Copyright (c) 2015 Michał Baryś
 * Licensed under the MIT license.
 */
(function($) {
  $.fn.jQueryTypingAnimationPlugin = function(options) {

    var settings = $.extend({
      // These are the defaults.
      startDelay: 0,
      speed: 50,
      skipTags: false,
      onStart: null,
      onTyping: null,
      onComplete: null
    }, options);

    return this.each(function(idx) {
      // Do something to each selected element.


      var $elem = $(this);

      if ($elem.prop("tagName") !== 'INPUT') {

        var sizes = [];
        console.log($elem.css('box-sizing'));
        switch ($elem.css('box-sizing')) {
          // case 'padding-box':
          //  sizes = [ $(this).innerWidth(), $(this).innerHeight() ];
          //  console.log('padding-box');
          // break;

          // case 'border-box':
          //  sizes = [ $(this).outerWidth(), $(this).outerHeight() ];
          //  console.log('border-box');
          // break;

          default: sizes = [$elem.width(), $elem.height()];
          console.log('content-box');

        }



        $elem.width(sizes[0]);
        $elem.height(sizes[1]);
      }
      $elem.addClass('typing-animation');



      var str,
        i = 0,
        isTag;

      if ($elem.prop("tagName") === 'INPUT') {
        str = $elem.val();
        $elem.val('');
        $elem.focus();
      } else {
        str = $elem.html();
        $elem.html('');
      }


      if (settings.skipTags === true) {
        str = str.replace(/(<([^>]+)>)/ig, "");
      }

      console.log('str', str);


      function type() {

        if (i === 0 && settings.onStart) {
          settings.onStart();
        }

        var text = str.slice(0, ++i);


        if (settings.onTyping) {
          settings.onTyping(i - 1, text.slice(-1), text);
        }
        if ($elem.prop("tagName") === 'INPUT') {
          $elem.val(text);
        } else {
          $elem.html(text);
        }

        var char = text.slice(-1);
        if (char === '<') {
          isTag = true;
        }
        if (char === '>') {
          isTag = false;
        }

        if (isTag) {
          return type();
        }

        if (text === str) {
          $elem.removeAttr('style');
          if (settings.onComplete) {
            settings.onComplete();
          }
          return;
        }

        setTimeout(type, settings.speed);

      };

      setTimeout(type, settings.startDelay);




    });
  };
}(jQuery));


// init plugin
$('p').jQueryTypingAnimationPlugin({
  startDelay: 0,
  speed: 100,
  skipTags: true,
  onStart: function() {
    console.log('start typing');
  },
  onTyping: function(idx, char, text) {
    //console.log( 'on index ' + idx + ' is letter "' + char + '", current text: "'+text+'"' ); 
  },
  onComplete: function() {
    console.log('Typing complete');
  }
});
.typing-animation {
  -webkit-transform: translateZ(0);
  -moz-transform: translateZ(0);
  -ms-transform: translateZ(0);
  -o-transform: translateZ(0);
  transform: translateZ(0);
}
.typing-animation:after {
  content: "|";
  -moz-animation: blink 1.1s steps(5, start) infinite;
  -webkit-animation: blink 1.1s steps(5, start) infinite;
  animation: blink 1.1s steps(5, start) infinite;
  font-weight: 100;
  display: inline-block;
}
@-moz-keyframes blink {
  to {
    visibility: hidden;
  }
}
@-webkit-keyframes blink {
  to {
    visibility: hidden;
  }
}
@keyframes blink {
  to {
    visibility: hidden;
  }
}
p {
  background: yellow;
  width: 300px;
  margin: 20px auto;
  padding: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p><em>Lorem</em> ipsum dolor <a href="#" target="_blank">sit amet</a>, consectetur adipisicing elit. Fuga, similique necessitatibus tenetur beatae suscipit, voluptatum repellat doloremque saepe, in dicta numquam. Ad impedit voluptatum ipsa id, consequuntur
  hic eaque dignissimos minima laboriosam, enim fuga praesentium commodi nisi laudantium non dolores voluptatibus veniam numquam eligendi fugit. Et, voluptas. Cum minus, cupiditate.</p>
r3mainer
  • 23,981
  • 3
  • 51
  • 88
1

Use .text() instead of .html()

On line 92:

$elem.text(text);

I don't know why this works other than to note that the browser is getting hung up inside jQuery's .html() method, but doesn't hang when using.text().

It is also worth noting that changing the string, makes a difference (try removing the space after necessitatibus).

It may have something to do with the browser not knowing where to re-render the ::after pseudo-element if the word boundary falls at the end of a line and there is no room for the blinking cursor.

As another answerer noted, simply changing the display value of the pseudo-element to inline-block in the CSS fixes the problem, too.

I suspect that by using the .html() method, the ::after element is re-rendered with every iteration, whereas by using .text(), only the text node gets updated on each iteration so the browser doesn't get hung up trying to re-render the ::after element.

/*
 *
 *
 *
 * Copyright (c) 2015 Michał Baryś
 * Licensed under the MIT license.
 */
(function($) {
  $.fn.jQueryTypingAnimationPlugin = function(options) {

    var settings = $.extend({
      // These are the defaults.
      startDelay: 0,
      speed: 50,
      skipTags: false,
      onStart: null,
      onTyping: null,
      onComplete: null
    }, options);

    return this.each(function(idx) {
      // Do something to each selected element.


      var $elem = $(this);

      if ($elem.prop("tagName") !== 'INPUT') {

        var sizes = [];
        console.log($elem.css('box-sizing'));
        switch ($elem.css('box-sizing')) {
          // case 'padding-box':
          //  sizes = [ $(this).innerWidth(), $(this).innerHeight() ];
          //  console.log('padding-box');
          // break;

          // case 'border-box':
          //  sizes = [ $(this).outerWidth(), $(this).outerHeight() ];
          //  console.log('border-box');
          // break;

          default: sizes = [$elem.width(), $elem.height()];
          console.log('content-box');

        }



        $elem.width(sizes[0]);
        $elem.height(sizes[1]);
      }
      $elem.addClass('typing-animation');



      var str,
        i = 0,
        isTag;

      if ($elem.prop("tagName") === 'INPUT') {
        str = $elem.val();
        $elem.val('');
        $elem.focus();
      } else {
        str = $elem.html();
        $elem.html('');
      }


      if (settings.skipTags === true) {
        str = str.replace(/(<([^>]+)>)/ig, "");
      }

      console.log('str', str);


      function type() {

        if (i === 0 && settings.onStart) {
          settings.onStart();
        }

        var text = str.slice(0, ++i);


        if (settings.onTyping) {
          settings.onTyping(i - 1, text.slice(-1), text);
        }
        if ($elem.prop("tagName") === 'INPUT') {
          $elem.val(text);
        } else {
          $elem.text(text);
        }

        var char = text.slice(-1);
        if (char === '<') {
          isTag = true;
        }
        if (char === '>') {
          isTag = false;
        }

        if (isTag) {
          return type();
        }

        if (text === str) {
          $elem.removeAttr('style');
          if (settings.onComplete) {
            settings.onComplete();
          }
          return;
        }

        setTimeout(type, settings.speed);

      };

      setTimeout(type, settings.startDelay);




    });
  };
}(jQuery));


// init plugin
$('p').jQueryTypingAnimationPlugin({
  startDelay: 0,
  speed: 100,
  skipTags: true,
  onStart: function() {
    console.log('start typing');
  },
  onTyping: function(idx, char, text) {
    //console.log( 'on index ' + idx + ' is letter "' + char + '", current text: "'+text+'"' ); 
  },
  onComplete: function() {
    console.log('Typing complete');
  }
});
.typing-animation {
  -webkit-transform: translateZ(0);
  -moz-transform: translateZ(0);
  -ms-transform: translateZ(0);
  -o-transform: translateZ(0);
  transform: translateZ(0);
}
.typing-animation:after {
  content: "|";
  -moz-animation: blink 1.1s steps(5, start) infinite;
  -webkit-animation: blink 1.1s steps(5, start) infinite;
  animation: blink 1.1s steps(5, start) infinite;
  font-weight: 100;
  /*width: 10px;
    min-height: 45px;
    height: 100%;
     background: #000;
     display: inline-block;*/
}
@-moz-keyframes blink {
  to {
    visibility: hidden;
  }
}
@-webkit-keyframes blink {
  to {
    visibility: hidden;
  }
}
@keyframes blink {
  to {
    visibility: hidden;
  }
}
p {
  background: yellow;
  width: 300px;
  margin: 20px auto;
  padding: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p><em>Lorem</em> ipsum dolor <a href="#" target="_blank">sit amet</a>, consectetur adipisicing elit. Fuga, similique necessitatibus tenetur beatae suscipit, voluptatum repellat doloremque saepe, in dicta numquam. Ad impedit voluptatum ipsa id, consequuntur
  hic eaque dignissimos minima laboriosam, enim fuga praesentium commodi nisi laudantium non dolores voluptatibus veniam numquam eligendi fugit. Et, voluptas. Cum minus, cupiditate.</p>
Community
  • 1
  • 1
gfullam
  • 11,531
  • 5
  • 50
  • 64
  • Simply changing the `display` to `inline-block` don't works for me, but now I've tested `$elem.empty().html()` and seems to be working. It's really weird, because when I have `skipTags: false`, then code above it's not hung up, and works fine. – Michał Baryś Jul 28 '15 at 14:38