Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smoothly changing text with JavaScript

Tags:

javascript

I'm making a landing page where a phrase is constantly changing with select words. For instance,

Design better websites
made for clients.

will switch the first or last word to become

Develop better websites
made for clients.

However, since "Develop" is a larger word than "Design", the rest of the text ends up getting pushed around without smoothly transitioning. Keep in mind, this is a multi-line sentence, and it is centered.

var first  = ['Create','Design','Develop'];
var second = ['you','clients','artists','us'];
var i = 0;
var j = 0;
var maxfirst  = first.length - 1;
var maxsecond = second.length - 1;

function delay() {
    $('#intro').velocity("transi1ion.slideUpIn", 1250);
    setInterval(firstwordchange, 400);
    setInterval(secondwordchange, 500);
}

function firstwordchange() {
    if (i < maxfirst) i++; else i = 0;

    $('#firstword').velocity("transition.slideUpOut", 300);

    setTimeout(function () {
        $('#firstword').text(first[i]);
    }, 200);

  $('#firstword').velocity("transition.slideUpIn", 300);
}

function secondwordchange() {
    if (j < maxsecond) j++; else j = 0;

    $('#secondword').velocity("transition.slideUpOut", 300);

    setTimeout(function () {
        $('#secondword').text(second[j]);
    }, 200);

    $('#secondword').velocity("transition.slideUpIn", 300);
}

setTimeout(delay, 700);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/velocity/1.1.0/velocity.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/velocity/1.1.0/velocity.ui.min.js"></script>
<div id="intro">
    <span id="firstword" class="introchange">Create</span>
    better websites made for
    <span id="secondword" class="introchange">you</span>.
</div>

How can I make the inner, non-changing text transition smoothly?

(actual website here)

like image 863
Alexander Lozada Avatar asked Dec 15 '14 01:12

Alexander Lozada


3 Answers

I'm going to write an outline of how I would do did it:

  1. Render the sentence using default initial values for the changing words and static positioning.
  2. Also render the other word variants with visibility: hidden so that you can determine their sizes.
  3. Absolutize every sentence part. From this point on everything will be absolutely positioned so it's best if you have a nice positioning context around it (usually done with position: relative on the parent).
  4. Measure every sentence part, both changing words and fixed sentence parts widths, including the hidden ones.
  5. When changing the words compute the differences between the old sizes and the new ones. Based on those differences, use some very simple Math to see how much parts should move left or right and apply a horizontal translation on them (and of course animate the translation - possibly just for what you want moving left/right, maybe you want other effects for the changing words).

Demo:

var first = ['Create','Cut','Reticulate'];
var second = ['you','clients','artists','us'];
var firstM = [], secondM = [], el;

var $first = $('.the-first'); 
var $second = $('.the-second'); 
var $container = $('#container');

// init static //    
$first.text(first[0]);
$second.text(second[0]);

// create measurables //
for(var i = 0; i < first.length; i++){
    el = $('<div class="measurable">' + first[i] + '</div>');
    $container.append(el);
    firstM.push(el.width());
}
for(var i = 0; i < second.length; i++){
    el = $('<div class="measurable">' + second[i] + '</div>');
    $container.append(el);
    secondM.push(el.width());
}

// absolutize //
var positions = [];
$('#container > span').each(function(){
    positions.push($(this).position());
});
$('#container > span').each(function(){
    var pos = positions.shift();
    $(this).css({
        position: 'absolute',
        left: pos.left,
        top: pos.top
    });
});

// remember initial sizes //
var firstInitialWidth = $first.width();
var secondInitialWidth = $second.width();

// loop the loop //
var activeWordsIndex = 0;
setInterval(function(){
    activeWordsIndex++;
    var firstIndex = activeWordsIndex % first.length;
    var secondIndex = activeWordsIndex % second.length;
    
    $first.text( first[firstIndex] );
    $second.text( second[secondIndex] );
    
    var firstLineOffset = (firstM[firstIndex] - firstInitialWidth) / 2;
    var secondLineOffset = (secondM[secondIndex] - secondInitialWidth) / 2;
   
    $('.static.first').css({
        transform: 'translateX(' + firstLineOffset + 'px)'
    });
    $('.static.second').css({
        transform: 'translateX(' + (-secondLineOffset) + 'px)'
    });
    
    $first.css({
        transition: 'none', 
        transform: 'translate(' + (-firstLineOffset) + 'px, -30px)',
        opacity: '0'
    });
    setTimeout(function(){
        $first.css({
            transition: 'all 1s ease',
            transform: 'translate(' + (-firstLineOffset) + 'px, 0px)',
            opacity: '1'
        });
    }, 50);
    
    $second.css({
        transition: 'none', 
        transform: 'translate(' + (-secondLineOffset) + 'px, 30px)',
        opacity: '0'
    });
    setTimeout(function(){
        $second.css({
            transition: 'all 1s ease',
            transform: 'translate(' + (-secondLineOffset) + 'px, 0px)',
            opacity: '1'
        });
    }, 50);
}, 2000);
#ubercontainer {
    border: 1px solid #eaeaea;
    border-radius: 2px;
    background-color: #ffefc6;
    width: 500px;
    margin: 20px auto;
    padding: 30px 0;
}
#container {
    position: relative;
    text-align: center;
    font-family: sans-serif;
    font-size: 32px;
    font-weight: 800;
    color: #4a6b82;
    height: 78px;
}
.measurable {
    position: absolute;
    visibility: hidden;
}

.static.first, .static.second {
    transition: transform 1s ease;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="ubercontainer">
<div id="container">
    <span class="the-first"></span> 
    <span class="static first">better websites </span><br />
    <span class="static second">made for</span> 
    <span class="the-second"></span>
</div>
</div>
like image 59
Alin Purcaru Avatar answered Oct 14 '22 19:10

Alin Purcaru


Writing a solution like this you will quickly come to realize you will have to abandon the idea of dynamic line breaks if your words differ in length by a reasonable amount.

That small detail aside, you can easily achieve the effect you're after using a standard pyramid of jQuery animate callback hell:

var target = $('#target');
var change = function(str) {
  var tmp = $('<h1>' + str + '</h1>');
  tmp.css({
      display: "inline-block",
      position: "absolute"
    })
    .appendTo('body')
    .hide();
  var targetWidth = tmp.outerWidth();
  tmp.remove();
  target.animate({
    opacity: 0
  }, 200, function() {
    target.animate({
      width: targetWidth
    }, 300, function() {
      target.empty()
        .html(str)
        .css({
          display: "initial"
        })
        .animate({
          opacity: 1
        }, 200);
    });
  });
}
var samples = [
  "some sample",
  "another example",
  "just"
];
var i = 0;
setInterval(function() {
  change(samples[++i % samples.length]);
}, 1400);
.container {
  margin: 0 auto;
  text-align: center;
}
#target {
  display: inline-block;
  vertical-align: bottom;
  white-space: no-wrap;
  height: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <h1>This is <span id="target"></span> text</h1>
  <h1>in a longer sentence</h1>
</div>
like image 9
Etheryte Avatar answered Oct 14 '22 20:10

Etheryte


Try using the Web Animation API

Element.animate();

Simplest reference: http://updates.html5rocks.com/2014/05/Web-Animations---element-animate-is-now-in-Chrome-36

like image 4
Edwin Reynoso Avatar answered Oct 14 '22 19:10

Edwin Reynoso