Javascript typing effect - javascript

The issue arises from the same issue as last time. My websites run off a static domain, so I want to be able to use this script on each site without making duplicate copies.
It functions as a typing text effect, I want to be able to define the text it prints out from the webpage itself and not the script.
Javascript
var index = 0;
var text = 'Text';
function type()
{
document.getElementById('screen').innerHTML += text.charAt(index);
index += 1;
var t = setTimeout('type()',100);
}
I've tried fiddling with the code and using them same method as my previous post, but I can't seem to get it to work.

Okay, I don't like any of the above code. Your original code also doesn't stop running once it reaches the end of the input text, and I don't believe any of the other suggested solutions stop either.
Here's a rewritten function in pure JS:
function type(i, t, ie, oe) {
input = document.getElementById(ie).innerHTML;
document.getElementById(oe).innerHTML += input.charAt(i);
setTimeout(function(){
((i < input.length - 1) ? type(i+1, t, ie, oe) : false);
}, t);
}
Which you can call like so:
type(0, 100, "text", "screen");
The parameters are: beginning index, speed, input element, output element
Your HTML will look something like this:
<div id="screen"></div>
<div id="text" style="display:none">Hello Bobby</div>
You can rename the divs to whatever you like, as long as you update the parameters accordingly. I'm sure there's an easier way to write this as well, but I like this method the most.
Demo
function type(i, t, ie, oe) {
input = document.getElementById(ie).innerHTML;
document.getElementById(oe).innerHTML += input.charAt(i);
setTimeout(function(){
((i < input.length - 1) ? type(i+1, t, ie, oe) : false);
}, t);
}
type(0, 100, "text", "screen");
<div id="screen"></div>
<div id="text" style="display:none">Hello Bobby</div>

Nice question, LMGTFY has often given me a giggle in the past. I think you may find the following to be pretty easy to throw around anywhere. It's just a few attributes added to your target container, along with a call to get the typewriter started.
Here, I run 4 of them simultaneously just for kicks. It's probably worth junking forEachNode in this example, instead using the few commented lines. If the result of getElementsByClassName was a true array, you could just call the .forEach method that arrays have. Unfortunately, a nodeList is similar but not the same - hence the need for such a function. I used it before realizing it probably clearer to do without it. In any case, it's a function I've found handy many times. I'll leave that in there as a thanks for such a fun question to consider.
function forEachNode(nodeList, func) {
var i, n = nodeList.length;
for (i = 0; i < n; i++) {
func(nodeList[i], i, nodeList);
}
}
window.addEventListener('load', mInit, false);
function typeWriter(el) {
var myDelay = el.getAttribute('keyDelay');
if (el.getAttribute('curIndex') == undefined)
el.setAttribute('curIndex', 0);
var curIndex = el.getAttribute('curIndex');
var curStr = el.getAttribute('typewriterdata');
el.innerHTML += curStr.charAt(curIndex);
curIndex++;
el.setAttribute('curIndex', curIndex);
if (curIndex < curStr.length)
setTimeout(callback, myDelay);
else {
if (el.getAttribute('nextline') != undefined) {
var nextTgt = el.getAttribute('nextline');
typeWriter(document.getElementById(nextTgt));
}
}
function callback() {
typeWriter(el);
}
}
function mInit() {
typeWriter(document.getElementById('line1'));
var i, n, elementList;
elementList = document.getElementsByClassName('autoType');
forEachNode(elementList, typeWriter);
// n = elementList.length;
// for (i=0; i<n; i++)
// typeWriter( elementList[i] );
}
.multi {
border: solid 2px #333333;
width: 400px;
}
<body>
<div class='autoType' typewriterdata='Enter this string letter by letter' keydelay='300'></div>
<div class='autoType' typewriterdata='Enter this string letter by letter' keydelay='200'></div>
<div class='autoType' typewriterdata='This is short but slooooow' keydelay='1000'></div>
<div class='autoType' typewriterdata='The rain falls mainly on the plain in Spain' keydelay='100'></div>
<div class='multi'>
<div id='line1' typewriterdata='This is line 1' keydelay='300' nextline='line2'></div>
<div id='line2' typewriterdata='This is line 2' keydelay='300' nextline='line3'></div>
<div id='line3' typewriterdata='This is line 3' keydelay='300' nextline='line4'></div>
<div id='line4' typewriterdata='This is line 4' keydelay='300'></div>
</div>
</body>

You can embed the text in the webpage itself in a hidden element like this:
HTML
<span id="hiddenText" style="display: none">Text you want to type out.</span>
and then you can get the text from the webpage itself like this:
Javascript
var text = document.getElementById('hiddenText').innerHTML;
Here is the jsfiddle you can see: http://jsfiddle.net/FMq6d/ .
This makes minimal changes to your code.

2 Years Later:
Check out this awesome Typing & erasing effect plus a blinking cursor - CodePen
In a Nutshell:
var index = 0;
var text = "The Typing Effect - In a Nutshell";
function type(){
var screenEl = $('#screen');
screenEl.html(text.substr(0, index++));
if (index < text.length) {
// Feel free to type
setTimeout('type()', 50);
} else {
// Reset and restart.
index = 0;
text = '';
}
};
type();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="screen"></p>

If you want to define what text it prints out, you should pass the text through an argument, if I understand your question correctly.
Try and mess with this:
var type = function( elem , text , index )
{
var index = index||0;
elem.innerHTML += text.charAt(index);
index++;
var t = setTimeout(function(){
type( elem , text , index );
},100);
}
type( document.getElementById('screen') , 'How\'re You?' );
<p id="screen">Hello, </p>

Here is an approach using promises for sleeping between key presses.
Here is a link for the repo at Github, but the code is basically this:
class Typer {
constructor(typingSpeed, content, output) {
this.typingSpeed = typingSpeed;
// Parses a NodeList to a series of chained promises
this.parseHtml(Array.from(content), output);
};
makePromise(node, output) {
if (node.nodeType == 1) // element
{
// When a new html tag is detected, append it to the document
return new Promise((resolve) => {
var tag = $(node.outerHTML.replace(node.innerHTML, ""));
tag.appendTo(output);
resolve(tag);
});
} else if (node.nodeType == 3) // text
{
// When text is detected, create a promise that appends a character
// and sleeps for a while before adding the next one, and so on...
return this.type(node, output, 0);
} else {
console.warn("Unknown node type");
}
}
parseHtml(nodes, output) {
return nodes.reduce((previous, current) => previous
.then(() => this.makePromise(current, output)
.then((output) => this.parseHtml(Array.from(current.childNodes), output))), Promise.resolve());
}
type(node, output, textPosition) {
var textIncrement = textPosition + 1;
var substring = node.data.substring(textPosition, textIncrement);
if (substring !== "") {
return new Promise(resolve => setTimeout(resolve, this.typingSpeed))
.then(() => output.append(substring))
.then(() => this.type(node, output, textIncrement));
}
return Promise.resolve(output);
}
}

let typeSpeed = 300;
let deleteSpeed = 200;
let wordDelay = 1000;
// utility function that returns a promise that resolves after t milliseconds
const delay = (t) => {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
//Change Current Job
const changeCurrentJob = async (wordsJson) => {
//Get Current Job
let currentJob = document.getElementById('wrap');
for (let wordFromJson of wordsJson) {
//Deleting
//Previous word letters count
let prevLetters = currentJob.innerHTML.split('');
//Loop letters with for of to remove them
for(let letterFromWordPrev of currentJob.innerHTML){
//Remove Last letter
prevLetters.pop();
//Join Letters Array
currentJob.innerHTML = prevLetters.join('');
await delay(deleteSpeed);
}
//Typing
for(let letterFromWord of wordFromJson){
currentJob.innerHTML = currentJob.innerHTML+letterFromWord;
//Type Speed
await delay(typeSpeed);
}
//After finishing word Wait
await delay(wordDelay);
}
//ReDO Typing - Declare Variables then Redo -
let words = document.getElementsByClassName('typewrite');
let wordsData = words[0];
let wordsJson2 = JSON.parse(wordsData.getAttribute('data-type'));
changeCurrentJob(wordsJson2);
}
// On window load Loop data-type And convert from json to txt and type
window.onload = function() {
let words = document.getElementsByClassName('typewrite');
let wordsData = words[0];
let wordsJson = JSON.parse(wordsData.getAttribute('data-type'));
setTimeout(changeCurrentJob,wordDelay,wordsJson);
};
<div class="typewrite" data-type='[ "Full Stack", "PHP", "JS" ]'>
<div class="styledWarp" id="wrap"></div>
</div>

Related

If array item includes string but does not include other string then remove string

I have a big block of text which I've split by new line, so each item in the array is a line of text.
I'm looping through these lines and trying to detect where a line includes a </mark> but doesn't include a <mark>, and if this condition has been met then it removes the </mark> (as it's missing an opening tag).
final_formatted_log_split = logtext.split("\n");
for (i = 0, l = final_formatted_log_split.length; i < l; i++) {
if (final_formatted_log_split[i].includes("<mark>") === false) {
if (final_formatted_log_split[i].includes("</mark>") === true) {
var removed_mark = final_formatted_log_split[i].replace("</mark>", "");
}
}
}
var final_formatted_log = final_formatted_log_split.join("\n");
console.log(final_formatted_log);
and this console log still includes in the text where it doesn't include a
Just to be super clear, the expected outcome is the following:
if a line is like this:
line of text here</mark>
then it needs to remove the </mark> because it does not contain an opening <mark>
I suspect it is to do with the === false, but from what I've read online that is how others have used .includes to see if something does 'not include'
You can do it with simple String.prototype.includes:
const arr = [
'<mark>1</mark>',
'2</mark>',
'3</mark></mark>',
'<mark>4</mark>',
]
const replaceMark = (arr) => {
return arr.map(e => {
if (e.includes('</mark>') && !e.includes('<mark>')) e = e.replace(/\<\/mark\>/g, '')
return e
})
}
console.log('original:', arr)
console.log('replaced:', replaceMark(arr))
This solution doesn't handle complex situations like <mark>text</mark></mark>, only the most basic ones.
There's nothing wrong with ===false.Its working properly.To check this one you just put a console.log inside if block.
What you are doing here is,you are not replacing array value with modified one.So replace this line
var removed_mark = final_formatted_log_split[i].replace("</mark>", "");
with
final_formatted_log_split[i] = final_formatted_log_split[i].replace("</mark>", "");
And you can use one if block instead of two if block.
var final_formatted_log_split = logtext.split("\n");;
for (i = 0, l = final_formatted_log_split.length; i < l; i++) {
if (!final_formatted_log_split[i].includes("<mark>") && final_formatted_log_split[i].includes("</mark>")) {
final_formatted_log_split[i] = final_formatted_log_split[i].replace("</mark>", "");
}
}
var final_formatted_log = final_formatted_log_split.join("\n");
console.log(final_formatted_log);

Changing a heading every few seconds, with an array and a loop

I'm trying to create a html heading that changes itself every 5 seconds, through an array of multiple items and a loop. I can get everything to work, except the text changing after every fifth second. Right now, it immediately goes from the first item in the array, to the last. I've tried using setTimeOut and setInterval, but no luck so far. I've also searched everywhere online.
Here's what i have so far, without the setTimeOut/setInterval part, because it didn't work:
var headingChange = {
heading: function() {
var headings = ['Hello', 'Hi', 'Ye'];
for (var i = 0; i < headings.length; i++) {
document.getElementById('heading').innerHTML = headings[i];
}
}
};
Here's a jsfiddle to make it easier (+ html).
https://jsfiddle.net/countermb/w9qwk6ch/
Hope someone can help me (am new to Javascript). Thanks in advance.
This Snippet might help you.
You may need to use setInterval to change the heading.
var headings = ['Hello', 'Hi', 'Ye'];
var i = 0;
var intervalId = setInterval(function() {
document.getElementById('heading').innerHTML = headings[i];
if (i == (headings.length - 1)) {
i = 0;
//you can even clear interval here to make heading stay as last one in array
//cleanInterval(intervalId);
} else {
i++;
}
}, 4000)
<h1 id="heading">Change this!</h1>
You can use setInterval to run your function every n milliseconds. Have also updated the snipped to just increment an index (or go back to 0) instead of looping through the array with a for loop.
var headingChange = {
currentHeader: 0,
headings: ['Hello', 'Hi', 'Ye'],
heading: function() {
document.getElementById('heading').innerHTML = this.headings[this.currentHeader];
if (this.currentHeader === this.headings.length - 1) {
this.currentHeader = 0;
} else {
this.currentHeader = this.currentHeader + 1;
}
}
};
setInterval(function(){
headingChange.heading();
}, 5000)

Why isn't my button working every time and why is it not printing the first 6 FIRST?

I am building a little project: A random quote generator.
I have 6 quotes stored as objects in a file, quotes.js. Each quote object has a quote and source. Some of them also have a citation and year.
I have an html page (plus CSS) that displays a quote on the screen. There is also a button to click: click to get a new quote.
My code largely runs, when I click the button on my page, it loads a new quote at random. The majority of the time...
However, I am also aiming to NOT display a random quote more than once until ALL quotes from the array have been displayed first.
This is not happening yet.
My button, at random, does not work. I may get 5 successful button clicks, a miss and then another success at random. I'm not sure why this is happening at all.
Can you please suggest what to do here? No errors are being caught in the console.
ACTUALLY, I suppose that the button IS working everytime, it's just loading the same quote again.
Here is my main code:
// event listener to respond to "Show another quote" button clicks
// when user clicks anywhere on the button, the "printQuote" function is called
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
// prints quote
function printQuote(){
var finalQuote = buildQuote();
document.getElementById('quote-box').innerHTML = finalQuote;
}
// builds message for html, adding on citation and/or year if necessary
function buildQuote(){
var quote2Print = getQuote();
var message;
message = "<p class='quote'>" + quote2Print.quote + "</p><p class='source'>" + quote2Print.source;
if(quote2Print.hasOwnProperty('citation') === true){
citation = quote2Print.citation;
message += "<span class='citation'>" + quote2Print.citation + "</span>";
if(quote2Print.hasOwnProperty('year') === true){
year = quote2Print.year;
message += "<span class='year'>" + quote2Print.year + "</span></p>";
return message;
} else {
return message += "</p>";
}
}else {
return message;
}
}
// makes sure that if all 6 quotes haven't been printed, getRandomQuote is called again until a new one is found
function getQuote(){
var countArray = [];
var quote;
if(countArray.length < 6){
quote = getRandomQuote();
while(countArray.indexOf(quote) === -1)
{
if(countArray.indexOf(quote) === -1) {
countArray.push(quote);
return quote;
} else{
quote = getRandomQuote();
}
}
} else {
quote = getRandomQuote();
return quote;
}
}
// With random number, goes through array of quotes and chooses one. random number = index position
function getRandomQuote(){
var randomQuoteNum = randomQuoteNo();
var quote = quotes[randomQuoteNum];
return quote;
}
// Gets a random number
function randomQuoteNo() {
var randomNumber = Math.floor(Math.random() * 6);
return randomNumber;
}
Going about this the easy way is, ofc, the easy way. So that's what I've done.
Basically you have an array of quotes that you load from somewhere. This you want shuffled (or randomised or whatever). And then you want to add one quote at a time to the quotes that are to be displayed? I have read your question as such and done a solution for that.
Here is a plnkr: http://plnkr.co/edit/CfdqQv1nGwdaIfYrbXr4?p=preview
And here is the code:
var quotes = [];
quotes.push(createQuote(1));
quotes.push(createQuote(2));
quotes.push(createQuote(3));
quotes.push(createQuote(4));
quotes.push(createQuote(5));
quotes.push(createQuote(6));
quotes.push(createQuote(7));
function createQuote(number) {
return {
id: number,
text: "text" + number,
author: "author" + number
};
}
var printedQuotes = [];
var nonPrintedQuotes = [];
init();
function init() {
nonPrintedQuotes = shuffle(quotes);
printVars();
}
function addQuoteToPrint() {
if (nonPrintedQuotes.length > 0) {
console.log("ADD QUOTE!");
var quote = nonPrintedQuotes.slice(-1, nonPrintedQuotes.length)
nonPrintedQuotes = nonPrintedQuotes.splice(0, nonPrintedQuotes.length - 1);
printedQuotes.push(quote[0]);
printVars();
} else {
console.log("No more quotes to print. Sorry. :/")
}
}
function shuffle(array) {
var m = array.length;
var shuffled = array.slice(); // Copy the array
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
var i = Math.floor(Math.random() * m--);
// And swap it with the current element.
var t = shuffled[m];
shuffled[m] = shuffled[i];
shuffled[i] = t;
}
return shuffled;
}
function printVars() {
console.log(quotes);
console.log(nonPrintedQuotes);
console.log(printedQuotes);
}
So basically, you load up your quotes somehow (I've created a little helper function just to easily create some fake quote objects). Then you create a shuffled array of those (don't know if you want to keep the original array sorted or not, so I left it sorted). Then when you say you want a quote, you take one element from the shuffled array and put in in the printed array.
I've used a fisher yates shuffle (as per EvanTrimboli's suggestion).
When you've loaded all the quotes, you can trigger your function to fetch more.

JavaScript 'input multiple' file duplication

This is my code:
var rowData = [];
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
rowData.push([input.files[i].name]);
};
console.log(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);
'rowData' recives a normal value for the first time i upload some thing.
At the second try it starts to duplicate incoming values.
If i uploaded 1 file first try it's ok.
Second time i wold get 2 same files.
Third 4 files, exetera.
Why this is happening?
UPD:
More explanations:
What happaning is: i upload x.doc my array is [x.doc], now i want to upload y.doc. I choose y.doc and my array turns in to [x.doc, y.doc, y.doc], then i upload z.doc and aaray looks like [x.doc, y.doc, y.doc, z.doc, z.doc, z.doc, z.doc]. And so on. It have to do some thing with length property, i messed it up some how.
UPD2:
Now the full version of the code:
//file upload
var rowData = [];
docAddButton.addListener('click', function () {
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
if (rowData.indexOf(true, input.files[i].name, Math.round(input.files[i].size * 0.001) + ' кб') === -1) {
rowData.push([
true,
input.files[i].name,
Math.round(input.files[i].size * 0.001) + ' кб'
]);
}
}
console.log(rowData);
tableModel.setData(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);
});
Thanx every one who helped!
Solution:
You can see from UPD2 that I have my function inside eventListener 'click'. What was happening is every time I pressed my input button it was reciving extra 'change' listener.
I changed .addListener for input to .addListenerOnce.
Presumably you're not reloading the page, and you're not clearing out rowData. You haven't shown how/where you define rowData, but it would appear that the same instance of it is reused between calls to showFiles. (If you haven't declared it anywhere, you're falling prey to The Horror of Implicit Globals.)
From your comments below, it sounds like you want to keep the entries in rowData, but you want to only add new entries. If so, you'll need an explicit check:
var showFiles = function () {
var input, i, file;
input = document.getElementById('doc-upload');
for (i = 0; i < input.files.length; i += 1) {
file = input.files[i].name;
if (indexOfFile(rowData, file) === -1) {
rowData.push([file]);
}
};
console.log(rowData);
};
function indexOfFile(data, file) {
var index;
for (index = 0; index < data.length; ++index) {
// Note that the 0 may need to be changed if the
// `rowData.push([file])` line above doesn't put
// the filename at index 0
if (data[index][0] === file) {
return index;
}
}
return -1;
}
Side note: You've said that the reason you're pushing an array rather than just the filename is that there's other information you're also including in that array but you left it out of your question. That's fine, but it may be delicate, see my note about index 0 above. You might consider using an array of objects instead:
rowData.push({
file: file,
other: "info",
goes: "here"
});
...and then in indexOfFile:
if (data[index].file === file) {
That way, the code is less susceptible to breaking if you change the structure of what you're pushing.
Basically,
rowData.push()
will just keep appending the elements to the end of your variable. You need to have a check if those inputs are already present.
(Edit-)
Try this:-
rowData=[];
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
var f=[];
for(var j=0;j<rowData.length;j++)
f.push(rowData[j][0]);
if(f.indexOf(input.files[i].name) == -1)
rowData.push([input.files[i].name]);
}
console.log(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);

jQuery - has class that starts with

What would be the best way to get all divs that have any class that starts with input? In other words, a and b should be returned from what's below, but not c.
<div id="a" class="input1 foo"></div>
<div id="b" class="foo input2"></div>
<div id="c" class="xinput3 foo"></div>
The ostensible way, which surprisingly was accepted here, is to do $("div[class^='input']"); but of course that misses b. And of course $("div[class*='input']"); will give a false positive on c.
The best I could come up with was this monstrosity
function getAllInputDivs() {
return $("div").filter(function (i, currentDiv) {
return $.grep($(currentDiv).attr("class").split(" "), function (val) {
return val.substr(0, "input".length) === "input";
}).length > 0;
});
}
Is there a cleaner way? Here's a working fiddle of the above
You can create your own expression in jQuery
$.expr[':'].hasClassStartingWithInput = function(obj){
return (/\binput/).test(obj.className);
};
and you can retrieve those div with
$('div:hasClassStartingWithInput');
a JsFiddle Example: http://jsfiddle.net/7zFD6/
Edit: you could also use a parameter (without hardcoding the class name inside the function identifier) in this way
$.expr[':'].hasClassStartingWith = function(el, i, selector) {
var re = new RegExp("\\b" + selector[3]);
return re.test(el.className);
}
new example on http://jsfiddle.net/pMepk/1/
Here's one way...
function getAllInputDivs() {
return $("div").filter(function () {
return /(?:^|\s)input/.test(this.className);
});
}
Or make it more versatile...
function classStartsWith( tag, s ) {
var re = new RegExp('(?:^|\\s)' + s);
return $(tag || '*').filter(function () {
return re.test(this.className);
});
}
Or take the indexOf approach if you don't like regex...
function classStartsWith( tag, s ) {
return $(tag || '*').filter(function () {
return this.className.indexOf(s)===0 || this.className.indexOf(' ' + s)>-1;
});
}
Though you should be aware that it does't test for tab characters, only space characters, so it could fail if a tab was used instead of a space.
Going back to the regex versions, you can improve efficiency by adding the searched string to the selector.
Then it is only testing a subset of divs.
function getAllInputDivs() {
return $("div[class*='input']").filter(function () {
return /(?:^|\s)input/.test(this.className);
});
}
With the .filter() applied to only those divs that you know have input somewhere in the class, the performance will improve.
Or the versatile version would look like this:
function classStartsWith( tag, s ) {
var re = new RegExp('(?:^|\\s)' + s);
return $((tag || '*') + '[class*="' + s + '"]').filter(function () {
return re.test(this.className);
});
}
This is my solution for the problem:
(function($) {
$.fn.hasClassStartsWith = function(klass) {
var _return = [];
for(var i = 0; i < this.length; i++){
if((' ' + $(this[i]).attr('class')).indexOf(klass) != -1)
_return.push(this[i]);
}
return _return;
}
})(jQuery);
Use it as follows:
var divs = $('div').hasClassStartsWith("my_class_prefix");
It works also for the case someone creates a class with a dot in the middle.

Categories