How to change every character while hovering? - javascript

I have word
<p class="item">Ultimatepp’s</p>
I need to change every letter while hovering
I have characters var characters = 'a#ldfhas?
kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr'; and i need to insert random character instead of random character in the word Ultimatepp’s.
var item = $('.item');
var arr = Array.from(item);
item.on('mouseenter', function () {
var characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr';
var charactersArr = Array.from(characters);
var self = $(this);
var itemText = self.html();
var arr = Array.from(itemText);
var reversed = arr.reverse();
var string = reversed.join('');
var x = charactersArr.splice(Math.random() * itemText.length, itemText.length);
$(this).html(x);
});
I wrote this code that changes the whole word, but i need to replace characters. Maybe this code could be rewritten?

I would wrap each letter in a span so you can bind a separate hover event to each:
var characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr',
intervalVar,
timeoutVar;
intervalCount = 0;
$('.item').html(function() { // this just wraps the original words letters in spans
var text = $(this).text(),
html = '';
for (i = 0; i < text.length; i++) {
html += `<span data-original="${text[i]}">${text[i]}</span>`;
}
return html;
})
.on('mouseenter', function() {
var $item = $(this);
changeChars($item); // run immediately when you hover
setupInterval($item);
})
.on('mouseleave', function() {
clearInterval(intervalVar); // clear interval
clearTimeout(timeoutVar); // clear timeout
originalChars($(this)); // reset word
});
function originalChars($elem) { // resets word
var $spans = $elem.children('span');
$spans.text(function() {
return $(this).data('original');
});
return $spans;
}
function setupInterval($elem) {
clearTimeout(timeoutVar);
intervalVar = setInterval(function() { // set new interval to change the characters every second you are hovering
changeChars($elem);
}, 200);
}
function changeChars($elem) {
var $spans = originalChars($elem); // reset word before changing again
var changed = [];
// number of letters to change in original word (up to half of them can change) or change to a constant - eg 4
var toChange = Math.floor(Math.random() * ($spans.length / 2));
for (i = 0; i < toChange; i++) { // change up to a max of toChange characters
var changing = Math.floor(Math.random() * $spans.length); // get a random character in the original string
if (!changed.includes(changing)) { // see if it has already been changed
changed.push(changing); // if not add to list of changed
$spans.eq(changing).text(characters.substr(Math.floor(Math.random() * characters.length), 1)); // randomise letter
}
}
intervalCount++;
if (intervalCount == 5) { // add a pause after 5 changes
intervalCount = 0;
clearInterval(intervalVar);
timeoutVar = setTimeout(function() {
setupInterval($elem);
}, 1500); // pause for 1.5 secs before starting loop again
}
}
body {
font-size: 30px;
}
/* for easier hovering! */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="item">Ultimatepp’s</p>

You can use a mouseenter event to start a timer that shuffles an array of letters that matches the hovered word, and writes them back to the html. Then the mouseleave event would stop the timer would clearInterval and set the original text back.
To be able to set the effect on multiple elements, the matching must be done with querySelectorAll and you need to iterate over the matched elements to set the events:
const spans = [...document.querySelectorAll("span")]; //array of spans
const originalTexts = spans.map(span => span.textContent); //original texts
let timer = null;
//forEach to iterate over each span and set the mouseenter event
spans.forEach(span => span.addEventListener("mouseenter", ()=> {
timer = setInterval(() => {
let currText = span.textContent.split('');
//shuffle the word letters
for (let i = currText.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = currText[i];
currText[i] = currText[j];
currText[j] = temp;
}
span.textContent = currText.join(''); //set the new word back
}, 350);
}));
//also iterate to set the mouseleave event
spans.forEach((span, index) => span.addEventListener("mouseleave", ()=> {
if (timer !== null){
clearInterval(timer);
}
span.textContent = originalTexts[index];
}));
<span>Hover Me</span><br>
<span>You can also hover me</span><br>
<span>or me!!</span><br>
If you want to randomize according to a characters list you only need to change the shuffling part to iterate over each letter and get a random one from the characters:
const characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr';
const spans = [...document.querySelectorAll("span")];
const originalTexts = spans.map(span => span.textContent);
let timer = null;
spans.forEach(span => span.addEventListener("mouseenter", ()=> {
timer = setInterval(() => {
let currText = span.textContent.split('');
//only this part is different now, that randomizes according to the characters
for (let i = 0; i < currText.length; ++i){
currText[i] = characters[Math.floor(Math.random() * characters.length)];
}
span.textContent = currText.join('');
}, 350);
}));
spans.forEach((span, index) => span.addEventListener("mouseleave", ()=> {
if (timer !== null){
clearInterval(timer);
}
span.textContent = originalTexts[index];
}));
<span>Hover Me</span><br>
<span>You can also hover me</span><br>
<span>or me!!</span><br>

Related

Javascript selector to effect the multiple elements

I've already created a recipe box and it works with Javascript queryselector which increases the portion number and decreases it.
The problem with queryselector is that it only effects on the first element with the specific class. So when I duplicated this element, nothing works on the second element.
// Recipe calculator plus/minus actions
const plusBtn = document.querySelector(".portion-plus");
const minusBtn = document.querySelector(".portion-minus");
const portionNumber = document.querySelector(".portion-number span.portion-counter");
// thenum = "foo3bar5".match(/\d+/)[0]
const portionWord = document.querySelector(".portion-number span.portion-word");
const contentAmount = document.querySelectorAll(".content-list li span.content-amount");
let initRecipeValue = [];
window.addEventListener("load", () => {
contentAmount.forEach(elem => {
initRecipeValue.push(elem.innerText);
});
});
let count = 1;
// Increasing the counter
if (plusBtn && minusBtn) {
plusBtn.addEventListener("click", () => recipeCalculation(0, 1));
minusBtn.addEventListener("click", () => recipeCalculation(1, -1));
}
const recipeCalculation = (limit, addSub) => {
if (count > limit) {
count += addSub;
//console.log(count);
portionNumber.textContent = count;
contentAmount.forEach((content, index) => {
content.textContent = initRecipeValue[index] * count;
});
portionWord.textContent = count > 1 ? "Portionen" : "Portion";
}
};

Hide/Show div p5.js

I am using the p5.js library, and I am working on a speech recognition - text to speech project. Kind of a chatbot.
Input is voice input which becomes a string.
I am outputting the result from a txt file, using a markov chain. Output is a string contained in a div.
My question is:
Is there a way to hide/show the div containing my input/output (.myMessage and .robotMessage) in intervals?
I want the whole screen first showing only the input when I am talking, then input disappears and only output showing, then when the computer voice finishes speaking my input is shown in the screen and so on...
Here some parts of the code, let me know if it is clear enough.
//bot
function setup() {
noCanvas();
//reads and checks into the text file
for (var j = 0; j < names.length; j++) {
var txt = names[j];
for (var i = 0; i <= txt.length - order; i++) {
var gram = txt.substring(i, i + order);
if (i == 0) {
beginnings.push(gram);
}
if (!ngrams[gram]) {
ngrams[gram] = [];
}
ngrams[gram].push(txt.charAt(i + order));
}
}
//voice recognition
let lang = 'en-US';
let speechRec = new p5.SpeechRec(lang, gotSpeech);
let continuous = true;
let interim = false;
speechRec.start(continuous, interim);
//text-to-speach
speech = new p5.Speech();
speech.onLoad = voiceReady;
function voiceReady() {
console.log('voice ready');
}
//input-ouput
function gotSpeech() {
if (speechRec.resultValue) {
var p = createP(speechRec.resultString);
p.class('myMessage');
}
markovIt();
chooseVoice();
speech.speak(answer);
}
}
and
function markovIt() {
var currentGram = random(beginnings);
var result = currentGram;
for (var i = 0; i < 100; i++) {
var possibilities = ngrams[currentGram];
if (!possibilities) {
break;
}
var next = random(possibilities);
result += next;
var len = result.length;
currentGram = result.substring(len - order, len);
}
var answer = result;
window.answer = answer;
var p2 = createP(answer);
p2.class('robotMessage');
}
how the HTML looks
<div class="container">
<div class="myMessage"></div>
<div class="robotMessage"></div>
</div>
Use select() to get a document element by its id, class, or tag name. e.g:
let my_div = select("myMessage");
Change the style of an element by style().
e.g hide:
my_div.style("display", "none");
e.g. show:
my_div.style("display", "block");
See also Toggle Hide and Show

Javascript Pagination Issue

I'm trying to make a simple Javascript pagination function, but I'm having this issue where instead of iterating through the array, it keeps adding new list items to the innerhtml.
I have tried creating an element and appending it to the DOM.
I have tried using if/else statements to display the list items I
want.
<body>
<div class='result'></div>
<button class="add">+</button>
<script src='pretty.js'></script>
</body>
let dogs = [
'goldendoodle',
'poodle',
'afghan hound',
'golden retriever',
'labrador',
'chihuahua',
'pitbull',
'german shepherd',
'greyhound',
'bull terrier'
]
let high = 1;
let low = 0;
let result = document.querySelector('.result');
let add = document.querySelector('.add');
function Pagination(low,high) {
for(var i = 0 ; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
if(i >= low && i < high) {
answer.style.display ='block';
}
if(i < low || i > high) {
answer.style.display ='none';
}
}
}
Pagination(low,high);
add.addEventListener('click', () => {
low += 2;
high += 2;
Pagination(low,high);
});
When I click the button, I want the next two array items to appear and replace the last two shown.
To use the approach you've outlined above you'll need to clear the innerHtml of the result element before appending new children. At the top of your Pagination function try result.innerHtml = '';.
That said if you are using a hide/show approach to paginate the list it would be more efficient to create the dom elements only once and modify the style.display property of each instead of clearing out the result and re-creating all of the answer divs on every click.
Your Pagination function only adds elements to the dom each time it is called.
You can either remove the existing elements every time Pagination is called, and render only those that should be displayed, e.g.:
function Pagination(low,high) {
result.innerHTML = ''; // remove all children of result
// only render the children which should be visible
for(var i = low ; i < high;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
Or you can use display: block; / display: none. (Will not scale very well with large lists)
function Pagination(low,high) {
// only append all dogs once
if(result.childElementCount === 0) {
for(var i = 0; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.style.display ='none';
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
// toggle display: none / block for each element
for(var i = 0; i < dogs.length;i++) {
if(i >= low && i < high)
answer.style.display ='block';
else
answer.style.display ='none';
}
}
As a bonus, heres a reusable pagination class example:
function Pagination(container, items) {
this.container = container;
this.result = container.querySelector('.result');
this.prevBtn = container.querySelector('.prev');
this.nextBtn = container.querySelector('.next');
this.items = items;
this.offset = 0;
this.limit = 5;
this.updateDom();
this.prevBtn.onclick = this.prevPage.bind(this);
this.nextBtn.onclick = this.nextPage.bind(this);
}
Pagination.prototype.nextPage = function() {
if((this.offset + this.limit) < this.items.length)
this.offset += this.limit;
this.updateDom();
};
Pagination.prototype.prevPage = function() {
if(this.offset >= this.limit)
this.offset -= this.limit;
this.updateDom();
};
Pagination.prototype.updateDom = function() {
this.result.innerHTML = '';
let stop = Math.min(this.offset + this.limit, this.items.length);
for(let i = this.offset; i < stop; i++) {
let el = document.createElement("div");
el.appendChild(document.createTextNode(this.items[i]));
this.result.appendChild(el);
}
let hasPrev = this.offset > 0;
if(hasPrev)
this.prevBtn.classList.remove('hide');
else
this.prevBtn.classList.add('hide');
let hasNext = (this.offset + this.limit) < this.items.length;
if(hasNext)
this.nextBtn.classList.remove('hide');
else
this.nextBtn.classList.add('hide');
};
let items = [];
for (let i = 1; i <= 50; i++)
items.push(`Item ${i}`);
let pagination = new Pagination(document.querySelector(".paginate"), items);
// You can also programatically switch to the next / prev page:
// pagination.nextPage();
// pagination.prevPage();
.hide { visibility: hidden; }
<div class="paginate">
<div class="result"></div>
<button class="prev">PREV</button>
<button class="next">NEXT</button>
</div>
Maybe this is along the lines of what you want to do?
It tracks only a globalIndex (which would be like like your 'low' variable).
The showNextTwoItems function:
- Notes the indexes where we should start and end
- Clears the container div
- Enters a while loop that appends items and increments the current index
- Updates the globalIndex when enough items have been added
let dogs = [ 'goldendoodle', 'poodle', 'afghan hound', 'golden retriever', 'labrador', 'chihuahua', 'pitbull', 'german shepherd', 'greyhound', 'bull terrier' ],
containerDiv = document.querySelector('.result'),
addBtn = document.querySelector('.add'),
globalIndex = 0; // Tracks where we left off (starts at zero)
const NUMBER_TO_SHOW = 2;
addBtn.addEventListener("click", showNextTwoItems); // Calls function on click
function showNextTwoItems(){
let numberToShow = NUMBER_TO_SHOW, // In case we ever want to change numberToShow
currentIndex = globalIndex, // Gets local copy of globalIndex (always < dogs.length)
// Lets us stop looping when we've shown enough or reach the end of the array
stopBeforeIndex = Math.min(currentIndex + numberToShow, dogs.length);
containerDiv.innerHTML = ""; // Clears div
while(currentIndex < stopBeforeIndex){
// Creates and appends a text node with the next dog
const newItem = document.createTextNode(dogs[currentIndex]);
containerDiv.appendChild(newItem);
// Creates and appends a line break
const lineBreak = document.createElement("BR");
containerDiv.appendChild(lineBreak);
// Moves on to the next index
currentIndex++;
}
// Updates global index (making sure it is not too big for the array)
globalIndex = currentIndex < dogs.length ? currentIndex : 0;
}
<button class="add">+</button>
<div class='result'></div>

Replacing a div of text with individual spans as letters and iterating through it

I am attempting to replace a div of text with individual span elements of each letter, which is straightforward but I'm trying to have those spans font size increase incrementally and I can't figure out what to throw in the setTimeout function.
<div id ="foo"> Welcome to Here </div>
function test() {
let word = document.getElementById('foo');
let arr = word.split("")
word.innerHTML = "";
arr.forEach((x,index) => {
let newSpan = document.createElement('span')
newSpan.innerHTML = x;
word.appendChild(newSpan);
setTimeout(() => {
????
}, 100 + index*100)
})
}
Do your split on the inner text of your element and the increase the font size of your span:
function test() {
const baseFontSize = 16; // starting font size
const word = document.getElementById('foo');
const text = word.innerText; // split this and not your element object
const arr = text.split("");
word.innerHTML = "";
arr.forEach((x, index) => {
let newSpan = document.createElement('span')
newSpan.innerHTML = x;
word.appendChild(newSpan);
setTimeout(function() {
newSpan.style.fontSize = (baseFontSize + (index * 2)) + 'px'; // increment by 2px
}, 1000 * index) // increment each span a second after the last
})
}
test();
<div id="foo">Welcome to Here</div>
he is another try with settimeout
function test() {
var fontSize = 12;
let word = document.getElementById('foo');
let arr = word.innerHTML.split("")
word.innerHTML = "";
arr.forEach((x,index) => {
var fs = (fontSize + index) +"px";
let newSpan = document.createElement('span')
newSpan.innerHTML = x;
word.appendChild(newSpan);
setTimeout(function(){
newSpan.style.fontSize = fs
}, 2000 + (index * 100));
})
}
test();
span{
display : inline-block;
font-size:12;
}
<div id ="foo"> Welcome to Here </div>
The problem with this code is that getDocumentById() will return a reference to the element, not the text itself.
If the inner content of your div is plain text, you can try this
var domElement = document.getElementById('foo');
var word = domElement.innerText; // Here you get the text itself
domElement.innerText = ''; // Here you clear the previous content which should be text
for (let i = 0; i < word.length; i++) {
var spanElement = document.createElement('span');
spanElement.innerText = word[i];
domElement.appendChild(spanElement);
}
Now if you want to increase the size of the span, you can do it using the counter of the loop as you can see here
spanElement.style.fontSize = i

Toggle that cycles through 3 states

I need to create one button that will cycle through 3 states. For some reason that is incredible challenging to find. The functions in each state are simple style changes, example:
click 1: hide div
click 2: show hidden div, change bg-color
click 3: reset bg-color, change font
click 1: reset font, hide div
Any ideas? I can't use jquery (class assignment, not allowed)
Cou could use an array, with a function for each state change, and a counter variable which cycles through the possible states.
Then simply invoke the current state function through an click handler of the button.
Something like this could do it
var toggle = (function (el) {
var div = document.getElementById(el); //Get the div you want to change
var states = []; //The Array to hold the functions
var style = {} //Will be used to save the current style
for (var att in div.style) //Saves the style of the div *I was just lazy and did a for loop*
style[att] = div.style[att]
var current = 0; //The Counter
states[0] = function () { //The first state function
div.style["font-family"] = style["font-family"]
div.style.display = "none";
};
states[1] = function () {
div.style.display = "block";
div.style["background-color"] = "rgb(" + [rand(), rand(), rand()] + ")"; // [1,2,3] toString is "1,2,3"
};
states[2] = function () {
div.style["background-color"] = style["background-color"];
div.style["font-family"] = "Courier New";
}
function rand() { //ONly to return a random number for a random bgcolor
return ~~(Math.random() * 255)
}
return function () { //The function which cycles through the states
states[current]() //Invokes the current statechange function
current = (current + 1) % (states.length); //Increments the counter and uses modulo to cycle
}
})("div");
document.getElementById("click")
.addEventListener("click", toggle);
Heres an example on JSFiddle
Update:
I modified it a bit and commented the changed code, this should be able of changing the states of multiple elements on a page
function rand() {
return~~ (Math.random() * 255);
}
var makeToggle = function (states, elements) { // I Changed it to makeToggle, The first argument accepts an array of states to cycle through, the second either an array of elements, or an array of objects with the element property (and an optional state function)
var current = 0; //Sets the counter to zero
for (var i = 0, ilen = elements.length; i < ilen; i++) {
if (!elements[i].element) { //check if you passed an Object with the `element` Property
elements[i] = {
element: elements[i] //If it was an array, the arrays element will be set to an object
}; //to support arrays only
}
elements[i].style = {}; //to save the original style in the object
for (var att in elements[i].element.style) {
elements[i].style[att] = div.style[att]; // saves it
}
}
function doForElements() { //Invokes either the state function passed with an element, or the general statefunction
for (var i = 0, ilen = elements.length; i < ilen; i++) {
var state = elements[i].states;
if (state && typeof state[current] === "function") state = state[current];
else state = states[current];
state(elements[i].element, elements[i].style); //Invokes the function with the element as first parameter and the original style as second
}
}
return function () { //Returns the function for the click handler
doForElements();
current = (current + 1) % (states.length); //cycles the current state counter
};
};
var states = []; //Here the General State change functions get defined
states[0] = function (div, style) {
div.style["font-family"] = style["font-family"];
div.style.display = "none";
};
states[1] = function (div, style) {
div.style.display = "block";
div.style["background-color"] = "rgb(" + [rand(), rand(), rand()] + ")";
};
states[2] = function (div, style) {
div.style["background-color"] = style["background-color"];
div.style["font-family"] = "Courier New";
};
var elements = [].slice.call(document.getElementsByTagName("div")); //To actually get an Array of the NodeList (all divs on the page)
elements[4] = { //changes the 5th element (which should receive a special statechange function)
element: elements[4],
states: {
1: function (div, style) { //Use an Objects property to pass an single state change instead of an array with functions
div.style.display = "block";
div.style["background-color"] = "yellow";
}
}
};
var toggle = makeToggle(states, elements); //sets the function for the click handler to toggle
//Pass an Object with the Elements and an optional st
document.getElementById("click")
.addEventListener("click", toggle); //binds the function
Heres a JSBin to try it out
<div id="mydiv" data-state="0"></div>
<input type="button" id="mybutton" value="Change State" />
var states = {
1: [{ name: 'display', value: 'none'}],
2: [{ name: 'display', value: 'block'}],
3: [{ name: 'background-color', value: 'white'}, { name: 'prop', value: 'val' }]
}
window.onload = function(){
var mydiv = document.getElementById('mydiv');
var mybutton = document.getElementById('mybutton');
mybutton.onclick = function (){
var num = parseInt(mydiv.getAttribute('data-state'));
num = num < 3 ? ++num : 1;
var nameValues = states[num];
for(var i = 0; i < nameValues.length; i++)
mydiv.style[nameValues[i].name] = nameValues[i].value;
}
}

Categories

Resources