What is the most elegant way of showing an html text letter by letter (like videogame captions) using CSS and JavaScript?
While I'm sure that his can be solved using a brute-force approach (say, splitting the characters and print them one by one using jQuery.append()), I'm hoping there's some CSS3 (pseudo-elements?) or JQuery magic to do this more elegantly.
Extra points if the solution considers inner HTML content.
HTML
<div id="msg"/>
Javascript
var showText = function (target, message, index, interval) {
if (index < message.length) {
$(target).append(message[index++]);
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
Call with:
$(function () {
showText("#msg", "Hello, World!", 0, 500);
});
If a smooth reveal is reasonable then I think this should be pretty straightforward. Untested, but this is how I imagine it would work
html
<div id="text"><span>The intergalactic space agency</span></div>
css
div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden; }
jQuery
var spanWidth = $('#test span').width();
$('#text').animate( { width: spanWidth }, 1000 );
Okay, I couldn't resist and made a fiddle. One little code error that I fixed. Looks good to me though!
http://jsfiddle.net/mrtsherman/6qQrN/1/
100% vanilla javascript, strict mode, unobtrusive html,
function printLetterByLetter(destination, message, speed){
var i = 0;
var interval = setInterval(function(){
document.getElementById(destination).innerHTML += message.charAt(i);
i++;
if (i > message.length){
clearInterval(interval);
}
}, speed);
}
printLetterByLetter("someElement", "Hello world, bonjour le monde.", 100);
You really should just append, or show/hide.
However, if for some odd reason you don't want to alter your text, you can use this overly-complicated-for-no-good-reason piece of code:
HTML:
<p>I'm moving slowly...<span class="cover"></span></p>
CSS:
p {
font-family: monospace;
float: left;
padding: 0;
position: relative;
}
.cover {
height: 100%;
width: 100%;
background: #fff;
position: absolute;
top: 0;
right: 0;
}
jQuery:
var $p = $('p'),
$cover = $('.cover'),
width = $p.width(),
decrement = width / $p.text().length;
function addChar()
{
$cover.css('width', '-=' + decrement);
if ( parseInt( $cover.css('width') ) < width )
{
setTimeout(addChar, 300);
}
}
addChar();
And finally, here's the fiddle: http://jsfiddle.net/dDGVH/236/
But, seriously, don't use this...
Make your code more elegant by preparing promises for each iteration, then execute them as second step where you can inject DOM logic.
const message = 'Solution using Promises';
const typingPromises = (message, timeout) =>
[...message].map(
(ch, i) =>
new Promise(resolve => {
setTimeout(() => {
resolve(message.substring(0, i + 1));
}, timeout * i);
})
);
typingPromises(message, 140).forEach(promise => {
promise.then(portion => {
document.querySelector('p').innerHTML = portion;
});
});
<div ><p></p></div>
This is the React version using hooks.
import React, { useState, useEffect } from "react";
import "./styles.css";
const App = ({ speed, msg }) => {
const Typer = ({ speed = 250, children = " Introduce your text" }) =>
{
const [idx, setidx] = useState(0);
useEffect(() => {
const timer = window.setInterval(() => setidx((v) => v + 1), speed);
return () => window.clearInterval(timer);
});
return <div>{children.substr(0, idx)}</div>;
};
return (
<div>
<Typer speed={speed} children={msg}></Typer>
</div>
);
};
export default App;
and the main index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const message2 = "This is some text to get typed on time";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App speed={100} msg={message2} />
</StrictMode>,
rootElement
);
Have a look at code sandbox
When I did this I ran into the problem of a word jumping from the end of one line to the begging of the next as it the letters appeared to get around this I used to side by side spans, one of which the text was transparent, the other visible and simply moved letters one by one from the invisible span to the visible. Here's a fiddle.
HTML
<div class='wrapper'>
<span class='visible'></span><span class='invisible'></span>
</div>
CSS
.visible {
color: black;
}
.invisible {
color: transparent;
}
JS
var text = "Whatever you want your text to be here",
soFar = "";
var visible = document.querySelector(".visible"),
invisible = document.querySelector(".invisible");
invisible.innerHTML = text;
var t = setInterval(function(){
soFar += text.substr(0, 1),
text = text.substr(1);
visible.innerHTML = soFar;
invisible.innerHTML = text;
if (text.length === 0) clearInterval(t);
}, 100)
I made a tiny jquery plugin for that. First you need to make sure that the text will be visible if javascript is disabled, and if not, redisplay the text letter by letter.
$.fn.retype = function(delay) {
var el = this,
t = el.text(),
c = t.split(''),
l = c.length,
i = 0;
delay = delay || 100;
el.empty();
var interval = setInterval(function(){
if(i < l) el.text(el.text() + c[i++]);
else clearInterval(interval);
}, delay);
};
Usage will be just as easy as this:
$('h1').retype();
This is based on armen.shimoon's:
var showText = function (target, message, index, interval) {
if (index <= message.length && $(target).is(':visible')) {
$(target).html(message.substr(0, index++));
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
message[index++] wasn't working in my jquery webpage - I had to change it to substr. Also my original text uses HTML and the text that is typed uses HTML formatting (br, b, etc). I've also got the function stopping if the target has been hidden.
Vanilla JavaScript version of #armen.shimoon's answer:
document.addEventListener('DOMContentLoaded', function() {
showText("#msg", "Hello, World!", 0, 100);
});
let showText = function (target, message, index, interval) {
if (index < message.length) {
document.querySelector(target).innerHTML =
document.querySelector(target).innerHTML + message[index++];
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
<div id="msg"></div>
<span id="text_target"></span>
You need to wrap each letter in span tag, because anonymous html elements cannot be styled. Then reveal one span at a time. This avoids some innerText / innerHTML issues (no DOM reflow?) but can be overkill in your case.
Vanillla
(function () {
var showText = function(target, msg, index, interval){
var el = document.getElementById(target);
if(index < msg.length){
el.innerHTML = el.innerHTML + msg.charAt(index);
index = index + 1;
setTimeout(function(){
showText(target,msg,index,interval);
},interval);
}
};
showText("id", "Hello, World!", 0, 50);
})();
you could improve on this code by changing it so you only get the el one time due to the fact that it takes a bit of resources to modify the DOM.
there is a good answer how to do it here:
this is a way that you can manipulate each letter with any .animate() property available, not with hacks like covering the text with s etc.
I was trying to solve the same problem and I came up with this solution that seems to work.
HTML
<div id='target'></div>
jQuery
$(function() {
var message = 'Hello world';
var index = 0;
function displayLetter() {
if (index < message.length) {
$('#target').append(message[index++]);
}
else{
clearInterval(repeat);
}
}
var repeat = setInterval(displayLetter, 100);
});
There are a couple libraries for this now. There are TypeItJs and TypedJS both of them allow for html to be in the thing that you are typing out as well as numerous other methods that assist in animating Typewriter effects.
The answer I will type is the answer Show text letter by letter , but I made some changes to it
HTML
<h1>
<span id="msg"></span>
</h1>
Javacript
var showText = function (target, message, index, interval) {
if (index < message.length) {
//$(target).append(message[index++]);
$(target).text($(target).text() + message[index++]);
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
$(function () {
showText("#msg", "Hello, World!", 0, 80);
//showText("#msg", "Hello, World!", 0, 500);
});
CSS
#msg {
color: #6d67c6;
font-family: sans-serif;
font-size: 30px;
font-weight: bold;
}
function textRoll(message, interval){
setTimeout(() => {
para.innerHTML += message.charAt(0);
return textRoll(message.slice(1))
}, interval)
}
try this with jquery
var spanWidth = $('#text span').width();
$('#text').animate( { width: spanWidth }, 3000 );
div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="text"><span>Hello World !</span></div>
Related
I am trying to recreate the idea of this video by creating a typewriting logo of the site that contains emojis. However, it seems that rendering emoji on browser takes a little while, so rhombus with a question mark appears for a moment.
I believe that it's somehow connected with the nature of dec/hex rendering, but failed to find some detailed sources on the topic as I can't clearly state the problem. I would be very grateful for any ideas to resolve this problem.
My JS, CSS, and HTML respectively:
// ES6 Class
class TypeWriter {
constructor(txtElement, words, wait = 3000) {
this.txtElement = txtElement;
this.words = words;
this.txt = '';
this.wordIndex = 0;
this.wait = parseInt(wait, 10);
this.type();
this.isDeleting = false;
}
type() {
// Current index of word
const current = this.wordIndex % this.words.length;
// Get full text of current word
const fullTxt = this.words[current];
// Check if deleting
if(this.isDeleting) {
// Remove char
this.txt = fullTxt.substring(0, this.txt.length - 1);
} else {
// Add char
this.txt = fullTxt.substring(0, this.txt.length + 1);
}
// Insert txt into element
this.txtElement.innerHTML = `<span class="txt">${this.txt}</span>`;
// Initial Type Speed
let typeSpeed = 300;
if(this.isDeleting) {
typeSpeed /= 2;
}
// If word is complete
if(!this.isDeleting && this.txt === fullTxt) {
// Make pause at end
typeSpeed = this.wait;
// Set delete to true
this.isDeleting = true;
} else if(this.isDeleting && this.txt === '') {
this.isDeleting = false;
// Move to next word
this.wordIndex++;
// Pause before start typing
typeSpeed = 500;
}
setTimeout(() => this.type(), typeSpeed);
}
}
// Init On DOM Load
document.addEventListener('DOMContentLoaded', init);
// Init App
function init() {
const txtElement = document.querySelector('.txt-type');
const words = JSON.parse(txtElement.getAttribute('data-words'));
const wait = txtElement.getAttribute('data-wait');
// Init TypeWriter
new TypeWriter(txtElement, words, wait);
}
#import url('https://fonts.googleapis.com/css?family=Raleway:300,400');
/* https://css-tricks.com/snippets/css/typewriter-effect/ typewritting effect */
:root{
--header-height: 2rem;
--h2-font-size: 1.25rem;
--normal-font-size: .938rem;
font-family: Raleway;
color: rgb(42, 42, 42);
}
.nav__logo {
overflow: hidden;
position: relative;
}
a {
text-decoration: none;
color: rgb(42, 42, 42);
}
<div>
<b>#alexpoov </b>
<span class="nav__logo txt-type" data-wait="3000" data-words='["🌻", "🤹", "✌", "💻", "☔", "🎹", "📸", "🤙"]'>
</span>
</div>
If all you want is to change the emoji, the code you have overcomplicates it.
Attempting to change as little of your existing code as possible, the example below should show the emoji for 800ms and hide it for 500ms.
class TypeWriter {
constructor(txtElement, words) {
this.txtElement = txtElement;
this.words = words;
this.wordIndex = 0;
this.type(true); // show at the beginning
}
type(show) { // added 'show' param to determine whether to show or hide emoji
if (!show) {
this.txtElement.innerText = '';
setTimeout(() => this.type(true), 500); // wait 500ms and show
return;
}
this.txtElement.innerText = this.words[this.wordIndex];
this.wordIndex++;
this.wordIndex %= this.words.length;
setTimeout(() => this.type(false), 800); // wait 800ms and hide
}
}
If you want a pseudo-cursor object in place, you can add something like this after the emoji:
<span class="nav__logo cursor">|</span>
You can style it how you wish.
I have 9 boxes in my html.
There is a value, id called 'lifepoint'.
There is a mouse-click function: click once & decrease one life point. This code is completed.
function decrementlife() {
var element = document.getElementById('lifepoint');
var value = element.innerHTML;
--value;
console.log(value);
document.getElementById('lifepoint').innerHTML = value;
if(value <= 0) { alert("Game Over!")};
}
Also, there is a css style, called 'crackbox'.
.crackbox {
position: relative;
background: linear-gradient(0deg, black, rgb(120, 120, 120));
width: 12vh;
height: 12vh;
border-radius: 30%;
margin: 5px;
}
I want to change all box class from 'box' to 'crackbox' if life point is zero. Therefore, all box style can be 'crackbox'.
The below code is fail...
$(document).ready(function () {
$(".box").each(function() {
document.getElementById('lifepoint').innerHTML = value;
if(value <= 0) {
".box".toggleClass('crackbox')
};
})
});
Instead of using document ready, call another function from decrement life if the value turns 0. I am writing the code for your help.
function decrementlife() {
var element = document.getElementById('lifepoint');
var value = element.innerHTML;
--value;
console.log(value);
document.getElementById('lifepoint').innerHTML = value;
if(value <= 0) { changeClass(); alert("Game Over!")};
}
function changeClass(){
$('.box').addClass('crackbox').removeClass('box');
}
Hope, it helps!!
The simplest way would be to use querySelectorAll and loop through the elements:
for(let i = 0, list = document.querySelectorAll(".box"); i < list.length; i++)
{
list[i].classList.toggle('crackbox');
}
Or shorter ES6 version:
[...document.querySelectorAll(".box")].forEach(el => el.classList.toggle('crackbox'))
I am trying to create a typewriter effect animate so when I type into the input box, a new message is displayed and animates.
I tried having a global char variable to go through each element of the array, but when I would pass another other array of letters, after typing text into the input box, the h1 would not be overwritten.
This was my attempt at that
// wrap each letter in output in a span
function createInduvidualLetterSpans(letterArr) {
if (textEl.innerHTML) {
textEl.innerHTML = ''
}
for (const letter of letterArr) {
const letterEl = document.createElement('span')
letterEl.textContent = letter
textEl.appendChild(letterEl)
}
return textEl
}
// animate each letter
let char = 0
function displayChars() {
const span = textEl.querySelectorAll('span')[char]
span.classList.add('load')
char++
if (char == textArr.length) {
complete()
return
}
}
function complete() {
clearInterval(timer)
timer = null
}
createInduvidualLetterSpans(textArr)
let timer = setInterval(displayChars, 10)
My next attempt was to try iterators and closure. I've just been reading up on those ideas and imeditly thought this would be a perfect use case for them. However, I got the text to animate forward, but I am getting a
span is unidentified
error and I am not sure why.
//turn text into an array of letters
const textEl = document.querySelector('h1')
const textArr = textEl.textContent.split('')
const explore = 'Your lack of desire has lead you towards a life of bordeom and dread. [[GAME OVER]]'.split('')
const userInput = document.getElementById('user-input')
textEl.textContent = ''
// iterator fn
function iterator(arr) {
let count = 0
const inner = {
next: function () {
const el = arr[count]
count++
arr[count] == undefined ? done = true : done = false
return {
value: el,
done
}
},
createSpan: function (letterArr) {
textEl.textContent = ''
for (const letter of letterArr) {
const letterEl = document.createElement('span')
letterEl.textContent = letter
textEl.appendChild(letterEl)
}
},
animateEachLetter: function () {
const span = textEl.querySelectorAll('span')[count]
span.classList.add('load')
count++
arr[count] == undefined ? done = true : done = false
}
}
return inner
}
const it = iterator(explore);
it.createSpan(explore)
const exploreRoom = () => {
it.createSpan(explore)
}
exploreRoom()
setInterval(it.animateEachLetter, 10)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
body {
max-width: 100%;
}
span {
opacity: 0;
}
span.load {
opacity: 1;
}
<div class="grid">
<h1>You wake, confused and disoriented. Something just does not feel quite right. You reach over to the night stand, find a pair of glasses, put them on (even though you do not wear glasses and stand up. Do you explore the room or go back to bed?</h1>
<form id="user-input">
<input id="user-text" class="input" type="text" name="text" autofocus>
</form>
</div>
With
setInterval(it.animateEachLetter, 10)
and
animateEachLetter: function () {
const span = textEl.querySelectorAll('span')[count]
span.classList.add('load')
count++
arr[count] == undefined ? done = true : done = false
}
you're calling animateEachLetter and trying to find span, change its class, and increment the count regardless. It sounds like you just need to check whether such a span exists first - if it doesn't, clear the interval. The iterator protocol makes things much more confusing than they need to be for this, you might consider removing it completely.
Also, your animateEachLetter actually animates a single letter, not each letter. Consider having an animateEachLetter method which actually animates every letter, which calls a different method (one which runs in the interval) which animates one letter:
const textEl = document.querySelector('h1');
const textArr = textEl.textContent.split('');
const explore = 'Your lack of desire has lead you towards a life of bordeom and dread. [[GAME OVER]]'.split('');
const userInput = document.getElementById('user-input');
function makeAnimator(arr) {
let count = 0;
return {
createSpan: function(letterArr) {
textEl.textContent = '';
for (const letter of letterArr) {
const letterEl = document.createElement('span');
letterEl.textContent = letter;
textEl.appendChild(letterEl);
}
},
animateEachLetter(ms) {
this.interval = setInterval(() => this.animateOneLetter(), ms);
},
animateOneLetter() {
const span = textEl.querySelectorAll('span')[count];
if (!span) {
clearInterval(this.interval);
return;
}
span.classList.add('load')
count++
arr[count] == undefined ? done = true : done = false
}
};
}
const animator = makeAnimator(explore);
animator.createSpan(explore)
animator.animateEachLetter(20);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
body {
max-width: 100%;
}
span {
opacity: 0;
}
span.load {
opacity: 1;
}
<div class="grid">
<h1>You wake, confused and disoriented. Something just does not feel quite right. You reach over to the night stand, find a pair of glasses, put them on (even though you do not wear glasses and stand up. Do you explore the room or go back to bed?</h1>
<form id="user-input">
<input id="user-text" class="input" type="text" name="text" autofocus>
</form>
</div>
I have two png pictures, which I want to add on the top of pictures, depending on what certain setting is applied on the certain profile. I found a decent tutorial which could help me, it is here: http://fmdesign.forumotion.com/t279-profile-field-for-custom-post-profiles
Now, it only says how to add borders, background-and text color to a certain picture. I found an other post here on Stackoverflow, but I don't have the faintest idea how to change it to suit my needs. The thread is here: Insert image object into HTML
Now, my code that I want to use looks like this (my browser doesn't throw any errors for me, but it can nevertheless be rich with inappropriate coding and bugs, I am new to scripts, please be patient with me):
var delikeret = document.getElementById("dkepkeret");
var eszakikeret = document.getElementById("ekepkeret");
function extraProfileImage() {
var field = 'Hovatartozás';
customProfile
({
value: 'Észak',
keret: eszakikeret,
});
customProfile
({
value: 'Dél',
keret: delikeret,
});
customProfile({ value: '.*?', remove: True }); // remove field from profiles
function customProfile(o) {
var reg = new RegExp('<span class="label"><span style="color:#[a-f0-9]{6};">'+field+'</span>\\s:\\s</span>(\\s|)'+o.value+'<br>','i');
for (var i = 0, p = $('.postprofile, .user, .postdetails.poster-profile'); i < p.length; i++) {
if (p[i].innerHTML.match(reg)) {
if (o.remove) p[i].innerHTML = p[i].innerHTML.replace(reg, '');
} else {
p[i].style.backgroundImage: "url('" + o.keret + "')";
//p[i].appendChild(o.keret);
//p[i].style.background = o.keret;
//p[i].style.backgroundPosition = "center center";
//p[i].id = getElementById("o.keret");
}
}
}
}
var info = 'Plugin developed by Ange Tuteur for customizing post profiles. For help with this plugin, please see the following link : http://fmdesign.forumotion.com/t279-profile-field-for-custom-post-profiles';
$(document).ready(function() {
extraProfileImage();
});
I would be glad to receive any help!
PS: I have done everything else according to the Forumotion tutorial, although the images with "dkepkeret" and "ekepkeret" are stored elsewhere on the page, could that be a problem?
Thanks in advance!
So my code works somehow, here it is how it looks now:
var delikeret = new Image();
var eszakikeret = new Image();
delikeret.src = 'http://p.coldline.hu/2018/01/22/2748437-20180122-B8YiFj.png';
eszakikeret.src = 'http://p.coldline.hu/2018/01/22/2748438-20180122-9sWitv.png';
function extraProfileImage() {
var field = 'Hovatartozás';
customProfile
({
value: 'Észak',
});
customProfile
({
value: 'Dél',
});
// customProfile({ value: '.*?', remove: True }); // remove field from profiles
function customProfile(o) {
var reg = new RegExp(''+field+'\s:\s(\s|)'+o.value+'','i');
for (var i = 0, p = $('.postprofile, .user, .postdetails.poster-profile'); i < p.length; i++) {
if (p[i].innerHTML.match(reg)) {
if (o.remove) p[i].innerHTML = p[i].innerHTML.replace(reg, '');
} else {
if (o.value == "Észak") {
p[i].append(eszakikeret);
} else if (o.value == "Dél") {
p[i].append(delikeret);
}
}
}
}
}
But if I change the value of a given profile, it doesn't append the image. How to fix that?
So I could finally make my code free of bugs, here it is how it looks.
function extraProfileImage() {
var field = 'Hovatartozás';
customProfile
({
value: 'Észak',
});
customProfile
({
value: 'Dél',
});
//customProfile({ value: '.*?', remove: true }); // remove field from profiles
function customProfile(o) {
var reg = new RegExp('<span class="label"><span style="color:#[a-f0-9]{6};">'+field+'</span>\\s:\\s</span>(\\s|)'+o.value+'<br>','i');
for (var i = 0, p = $('.postprofile, .user, .postdetails.poster-profile'); i < p.length; i++) {
if (p[i].innerHTML.match(reg)) {
if (o.remove) p[i].innerHTML = p[i].innerHTML.replace(reg, '');
} else {
if (o.value == "Észak") {
p[i].classList.add("eszakikeret");
} else if (o.value == "Dél") {
p[i].classList.add("delikeret");
}
}
}
}
}
var info = 'Plugin developed by Ange Tuteur for customizing post profiles. For help with this plugin, please see the following link : http://fmdesign.forumotion.com/t279-profile-field-for-custom-post-profiles';
$(document).ready(function() {
extraProfileImage();
});
I had to set the .postprofile width to 210px so the border image doesn't stretch out for nothing.
The code added to my CSS is the following:
/* custom profile default*/ .postprofile, .user, .postdetails.poster-profile {
position: relative;
border:1px solid transparent;
padding:3px;
margin:3px;
z-index: 1;
}
/* ipb fix */ #ipbwrapper .postprofile {margin:0;
position: absolute;
top: 0;
left: 0;
}
.delikeret{
border-image-source: url(http://p.coldline.hu/2018/01/22/2748438-20180122-9sWitv.png);
border-image-slice: 20%;
border-image-outset: 10px;
border-image-width: 60px;
border-image-repeat: round;
}
.eszakikeret{
border-image-source: url(http://p.coldline.hu/2018/01/22/2748437-20180122-B8YiFj.png);
border-image-slice: 20%;
border-image-outset: 10px;
border-image-width: 60px;
border-image-repeat: round;
}
So I made it. If anyone would show future interest.
I have a sentence where I fade in one word and replace it with another from an array. However, since the words all vary in length, the sentence width abruptly changes and it results in a choppy transition.
How can I animate the width change? I tried adding a transition to the container of the sentence in css but that didn't work. I applied the transition as 1.5s all linear, so it should be animating the width as well as everything else whenever there is change. Any ideas?
$(function() {
var hello = ['dynamic', 'a', 'aklsjdlfajklsdlkf', 'asdf'];
var used = ['dynamic'];
var greeting = $('#what');
var item;
function hey() {
item = hello[Math.floor(Math.random() * hello.length)];
if (hello.length != used.length) {
while (jQuery.inArray(item, used) != -1) {
item = hello[Math.floor(Math.random() * hello.length)];
}
used.push(item);
} else {
used.length = 0;
item = hello[Math.floor(Math.random() * hello.length)];
used.push(item);
}
greeting.html(item);
greeting.animate({
"opacity": "1"
}, 1500);
}
window.setInterval(function() {
greeting.animate({
"opacity": "0"
}, 1500);
setTimeout(hey, 1500)
}, 5000);
});
#sentence {
transition: 1.5s all linear;
}
#what {
font-style: italic;
text-decoration: underline;
color: red;
}
<p id="sentence">
This is a sentence that has <span id="what">dynamic</span> text that alters width.
</p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
EDIT: Sorry if I was unclear, I only want to fade out the word, not the entire sentence. I'm trying to animate the width to fit the new word. I don't want to change/add any elements, just solve with the current tags in place.
function dataWord () {
$("[data-words]").attr("data-words", function(i, d){
var $self = $(this),
$words = d.split("|"),
tot = $words.length,
c = 0;
// CREATE SPANS INSIDE SPAN
for(var i=0; i<tot; i++) $self.append($('<span/>',{text:$words[i]}));
// COLLECT WORDS AND HIDE
$words = $self.find("span").hide();
// ANIMATE AND LOOP
(function loop(){
$self.animate({ width: $words.eq( c ).width() });
$words.stop().fadeOut().eq(c).fadeIn().delay(1000).show(0, loop);
c = ++c % tot;
}());
});
}
// dataWord(); // If you don't use external fonts use this on DOM ready; otherwise use:
$(window).on("load", dataWord);
p{text-align: center;font-family: 'Open Sans Condensed', sans-serif;font-size: 2em;}
/* WORDS SWAP */
[data-words]{
vertical-align: top;
position: static;
}
[data-words] > span{
position: absolute;
color: chocolate;
}
<link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300' rel='stylesheet' type='text/css'>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
We provide
<span data-words="code|solutions|design"></span>
for your business.
</p>
<p>
You ordered
<span data-words="1|3|28"></span>
<b>big</b>
<span data-words="salad|macs|chips"></span>
</p>
When you set new word for your sentence, you can save #what width and then make an animation with this width too. Like this:
// declare as global variable and update when you set new word
var width = greeting.css('width');
// animation
greeting.animate({
"opacity": "0", "width": width
}, 1500, function(){
});
I have had the same problem and went with a different approach, not fading but typing: jsfiddle demo
function type($el, text, position) {
if (text.length >= position) {
var rchars = 'qbvol'; // typo chars
if (position % 3 == 0 && Math.random() > .85) { // insert typo!
var typo;
var chr = text.substr(position, 1);
if (chr == chr.toUpperCase()) { typo = chr.toLowerCase(); }
else { typo = rchars.substr(Math.floor(Math.random() * rchars.length), 1); }
$el.text(text.substring(0, position - 1) + typo + '_');
setTimeout(function() { type($el, text, position - 1); }, 200)
}
else {
$el.text(text.substring(0, position) + '_');
setTimeout(function() { type($el, text, position + 1); }, 150)
}
}
else {
setTimeout(function() { $el.text(text); }, 400)
}
}
It basically inserts your new text on the page, with a nice caret and typo to make it look like someone is typing it.
Try this out:- http://jsfiddle.net/adiioo7/c8fFU/13/
You can update the sentence effect depending upon your requirement. Currently it is using fadein/fadeout.
JS:-
$(function () {
var hello = ['jupiter', 'a', 'aklsjdlfajklsdlkf', 'asdf'];
var used = ['jupiter'];
var greeting = $('#what');
var item;
var sentence = $('#sentence');
function hey() {
item = hello[Math.floor(Math.random() * hello.length)];
if (hello.length != used.length) {
while (jQuery.inArray(item, used) != -1) {
item = hello[Math.floor(Math.random() * hello.length)];
}
used.push(item);
} else {
used.length = 0;
item = hello[Math.floor(Math.random() * hello.length)];
used.push(item);
}
greeting.html(item);
greeting.animate({
"opacity": "1"
}, 1500);
sentence.fadeIn(1500);
}
window.setInterval(function () {
sentence.fadeOut(1500);
greeting.animate({
"opacity": "0"
}, 1500);
setTimeout(hey, 1500);
}, 5000);
});