I made this small animation so I can practice, its purpose is to add the last letter to the beginning of the word.
I've thought that it should work with ".innerTEXT" instead of .data too, but it doesn't. Can you explain why and how does .data work? On w3schools I've learnt that .data returns a URL, so wouldn't this be supposed to work with .innerTEXT?
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
const node = div.childNodes[0];
let text = node.data;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
node.data = text;
}, 100);
});
<div id="1">asdf</div>
See the working example with the innerText below.
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
let text = div.innerText;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
div.innerText = text;
}, 100);
});
<div id="1">asdf</div>
You can also do this with the node like you did, but you should use textContent:
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
const node = div.childNodes[0];
let text = node.textContent;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
node.textContent = text;
}, 100);
});
<div id="1">asdf</div>
Related
So I have a span element whose textContent changes when I hover over it.
I want the old text to fade out, then fade in the new text.
Also, since the new text is longer than the old one, the width of the span needs to increase smoothly to the new value.
The solution below basically does what I want, but I am not satisfied with it for two reasons:
It splits the animation effect in two Animations that are played one after another.
This is not elegant and it causes a slight glitch in some browsers (Chrome and Edge, but not Firefox) at the point where the first animation finishes.
Determining the old and new width "manually", by simply changing the text and using getComputedStyle, also seems quite ugly.
Also I'm not sure it will always work because getComputedStyle might not return the width in pixels.
Concerning (1), there is a different solution where I only have one Animation and the text gets changed in the middle using setTimeout.
This doesn't have the glitch, but it is likewise not elegant.
Question: What is the best way to achieve the effect described above?
Below is my current solution:
const initialText = "Hover here";
const finalText = "Text changed on hover";
const dur = 1500; // in milliseconds
const element = document.getElementById("my-element");
element.textContent = finalText;
const finalWidth = parseFloat(window.getComputedStyle(element).width);
element.textContent = initialText;
const initialWidth = parseFloat(window.getComputedStyle(element).width);
const intermediateWidth = ( initialWidth + finalWidth ) / 2;
const keyframes_0 = new KeyframeEffect(
element,
[{ opacity: 1, width: initialWidth + "px" }, { opacity: 0, width: intermediateWidth + "px" }],
{ duration: dur / 2 }
);
const keyframes_1 = new KeyframeEffect(
element,
[{ opacity: 0, width: intermediateWidth + "px" }, { opacity: 1, width: finalWidth + "px" }],
{ duration: dur / 2 }
);
const animation_0 = new Animation( keyframes_0, document.timeline );
const animation_1 = new Animation( keyframes_1, document.timeline );
element.addEventListener('mouseenter', () => {
animateTextChange(element, animation_1, animation_0, finalText, 1.0);
});
element.addEventListener('mouseleave', () => {
animateTextChange(element, animation_0, animation_1, initialText, -1.0);
});
function animateTextChange(elem, anim0, anim1, text, rate) {
if( anim0.playState === "running" ) {
anim0.onfinish = () => { };
anim0.playbackRate = rate;
anim0.play();
} else {
anim0.onfinish = () => { };
anim1.onfinish = () => {
elem.textContent = text;
anim0.playbackRate = rate;
anim0.play();
};
anim1.playbackRate = rate;
anim1.play();
}
}
<span id="my-element" style="display:inline-block; white-space:nowrap; overflow:clip">
Hover here
</span>
… more text
I'm using the 'timeupdate' event listener to sync a subtitle file with audio.
It is working currently, but I'd like to adjust it to where it is just highlighting the corresponding sentence in a large paragraph instead of deleting the entire span and replacing it with just the current sentence. This is the sort of functionality I am trying to replicate: https://j.hn/lab/html5karaoke/dream.html (see how it only highlights the section that it is currently on).
This is made complicated due to timeupdate constantly checking multiple times a second.
Here is the code:
var audioSync = function (options) {
var audioPlayer = document.getElementById(options.audioPlayer);
var subtitles = document.getElementById(options.subtitlesContainer);
var syncData = [];
var init = (function () {
return fetch(new Request(options.subtitlesFile))
.then((response) => response.text())
.then(createSubtitle);
})();
function createSubtitle(text) {
var rawSubTitle = text;
convertVttToJson(text).then((result) => {
var x = 0;
for (var i = 0; i < result.length; i++) {
if (result[i].part && result[i].part.trim() != '') {
syncData[x] = result[i];
x++;
}
}
});
}
audioPlayer.addEventListener('timeupdate', function (e) {
syncData.forEach(function (element, index, array) {
if (
audioPlayer.currentTime * 1000 >= element.start &&
audioPlayer.currentTime * 1000 <= element.end
) {
while (subtitles.hasChildNodes()) {
subtitles.removeChild(subtitles.firstChild);
}
var el = document.createElement('span');
el.setAttribute('id', 'c_' + index);
el.innerText = syncData[index].part + '\n';
el.style.background = 'yellow';
subtitles.appendChild(el);
}
});
});
};
new audioSync({
audioPlayer: 'audiofile', // the id of the audio tag
subtitlesContainer: 'subtitles', // the id where subtitles should show
subtitlesFile: './sample.vtt', // the path to the vtt file
});
I have a problem with my script, and that is that I want to play an audio when I click on a .bbp button, but this button is inside a hidden div that is then cloned.
Only when the cloned div becomes visible in the DOM, I want to play an audio when I click on .bbp, but it does not work for me.
SEE DEMO LIVE (Codepen) - The Snippet does not run on Stackoverflow
Note that if you comment #products, the audio assigned to .bbp yes will play, otherwise it will NOT play, since the audio
script can not identify if #products is visible in the DOM or not.
So, first I need to know that .bbp is visible, and I can not find how I can do it.
Any idea...?
Thanks in advance!
//-----------------
HTML & CSS
#products {display:none}
#derecha {display:none}
<div class="comprar">Clone 1</div> <!--Clone the div from "products" to "derecha"-->
<div class="bbp">X</div> <!--Delete the cloned div placed into "derecha"-->
SCRIP (Play Audio)
let audioHolderComprar = {};
$('.comprar').click(()=>{
let tempIdentifier = Date.now();
audioHolderComprar[tempIdentifier] = new Audio('comprar.mp3');
audioHolderComprar[tempIdentifier].play();
setTimeout(() => {
delete audioHolderComprar[tempIdentifier];
}, audioHolderComprar[tempIdentifier].duration + 1200);
});
//------------------
let audioHolderBorrar = {};
$('.bbp').click(()=>{
let tempIdentifier = Date.now();
audioHolderBorrar[tempIdentifier] = new Audio('borrar.mp3');
audioHolderBorrar[tempIdentifier].play();
setTimeout(() => {
delete audioHolderBorrar[tempIdentifier];
}, audioHolderBorrar[tempIdentifier].duration + 1200);
});
As I've mentioned in my comment, you have two places where you handle the click event for .bpp - these interfere with each other.
Also you're mixing the places where you should add html and javascript code. Though it works, it's a little bit messy.
Replace all of the content in your HTML pane on the left by this:
<div id="container">
<div id="productos">
<!-- =============== -->
<div id="cont-p1" class="cont-p">
<div id="producto-1">
<div class="img-prod"><img src="https://upload.wikimedia.org/wikipedia/commons/3/39/Lichtenstein_img_processing_test.png"></div>cont-p1 cloned!<br><br>Input Value = 1</div>
<input class="add-prod" type="num" value="1">
<div class="bbp">X</div></div>
</div> <!-- // productos -->
<div class="derecha" id="derecha"></div> <!-- // div derecha -->
<div id="comp-p1" data-clone="cont-p1" class="comp-clone comprar">Clone 1</div>
<div class="cont-num" id="clicks">0</div>
<div class="cont-num" id="clicksdos">0</div>
<div id="cont-resultado">
<input name="total" id="total">
</div>
<div id="cont-note">How to play the audio on the button to close the cloned div <span>.bbp</span><br>( <span class="red">X</span> ),<br>if the audio script can not know that it has been cloned...?
<br><br>
Note the CSS (line 3) that the div container of the all div´s that must be cloned is in <span>display=none</span>, but if you comment this line it can reproduce the audio onclick in the X button</div>
</div> <!-- // container -->
and all of the following goes into the JS pane to the right:
/*
https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js
*/
let audioHolderComprar = {};
$('.comprar').click(()=>{
let tempIdentifier = Date.now();
audioHolderComprar[tempIdentifier] = new Audio('https://notificationsounds.com/soundfiles/8b16ebc056e613024c057be590b542eb/file-sounds-1113-unconvinced.mp3');
audioHolderComprar[tempIdentifier].play();
// removing after play process gets over so if won't consume memory
setTimeout(() => {
delete audioHolderComprar[tempIdentifier];
}, audioHolderComprar[tempIdentifier].duration + 1200 /* you can remove threshold value if you wants to */);
});
//------------------
let audioHolderBorrar = {};
let clicks = 0;
let clicksdos = 0;
const safeInt = (key) => {
let value = parseInt(getValue(key));
return (isNaN(value) || value < 0) ? 0 : value;
}
// This loads our clicks from the LocalStorage
const loadClicks = () => {
clicks = safeInt('clicks');
clicksdos = safeInt('clicksdos');
}
const loadHTML = () => {
return getValue('html', '');
}
const loadFromStorage = () => {
let html = loadHTML();
if (html !== '') {
loadClicks();
}
displayClicks();
document.querySelector(".derecha").innerHTML = html;
}
// Display the clicks on the screen
const displayClicks = () => {
clicks = (clicks === NaN) ? 0 : clicks;
clicksdos = (clicksdos === NaN) ? 0 : clicksdos;
document.querySelector('#clicks').innerHTML = clicks;
document.querySelector('#clicksdos').innerHTML = clicksdos;
// Hide / Show Result
let display = (clicks > 0) ? 'block' : 'none';
document.querySelector('#cont-resultado').style.display = display;
document.querySelector('.derecha').style.display = display;
//document.querySelector('#aviso-producto-agregado').style.display = "block";
}
const adjustClicks = (value) => {
clicks += value;
clicksdos += value;
storeValue('clicks', clicks);
storeValue('clicksdos', clicksdos);
displayClicks();
}
const addClick = () => adjustClicks(1);
const removeClick = () => adjustClicks(-1);
// Manage localStorage
const storeValue = (key, value) => (localStorage) ? localStorage.setItem(key, value) : '';
const getValue = (key, defaultValue) => (localStorage) ? localStorage.getItem(key) : defaultValue;
const storeHTML = () => storeValue("html", document.getElementsByClassName("derecha")[0].innerHTML);
// Add a node to the Derecha
const addToDerecha = (nodeId) => {
let node = document.querySelector(`#${nodeId}`);
document.querySelector('.derecha').appendChild(node.cloneNode(true));
storeHTML();
displaySuma();
};
// Monitor ALL click events
document.addEventListener('click', (event) => {
let target = event.target;
// Add
if (target.matches('.comp-clone')) {
addClick();
addToDerecha(event.target.dataset.clone);
}
// Remove
if (target.matches('.bbp')) {
let tempIdentifier = Date.now();
audioHolderBorrar[tempIdentifier] = new Audio('https://notificationsounds.com/soundfiles/99c5e07b4d5de9d18c350cdf64c5aa3d/file-sounds-1110-stairs.mp3');
audioHolderBorrar[tempIdentifier].play();
// removing after play process gets over so if won't consume memory
setTimeout(() => {
delete audioHolderBorrar[tempIdentifier];
}, audioHolderBorrar[tempIdentifier].duration + 1200 /* you can remove threshold value if you wants to */);
getParent('.derecha', target).removeChild(target.parentNode);
removeClick();
storeHTML();
displaySuma();
}
});
// This is just a helper function.
const getParent = (match, node) => (node.matches(match)) ? node : getParent(match, node.parentNode);
// New Script for sum inputs
//const displaySuma = () => document.getElementById("total").value = suma();
const displaySuma=()=>document.getElementById("total").value=suma().toLocaleString("es-ES");
const suma = function() {
return Array.from(document.querySelectorAll(".derecha div .add-prod"))
.reduce((a, v) => a + parseFloat(v.value), 0);
}
// Code to run when the document loads.
document.addEventListener('DOMContentLoaded', () => {
if (localStorage) {
loadFromStorage();
}
displaySuma();
});
</script>
<script>
// Displays the new product alert added when the scroll is detected in the div #derecha
var displaced = document.getElementById('derecha')
if (displaced.scrollHeight > displaced.offsetHeight) {
document.getElementById("notice-product-added").style.display = "block";
};
// LocalStorage for the div #notice-product-added
const showMsgCart=localStorage.getItem('showMsgCarrito');if(showMsgCart==='false'){$('#notice-product-added').hide();}$('#notice-product-added').on('click',function(){$('#notice-product-added').fadeOut('slow');localStorage.setItem('showMsgCarrito','false');});
After that you should hear the closing sound.
I'm having trouble with my function. I made a text editor with BBcode.
Its working very well but the cursor always get back to the end of the textarea.
Here is how it works;
var element = document.getElementById('textEdit');
var lastFocus;
$(document.body).on('click','.editBout', function(e) {
e.preventDefault();
e.stopPropagation();
var style = ($(this).attr("data-style"));
// Depending on the button I set the BBcode
switch (style) {
case 'bold':
avS = "[b]";
apS = "[/b]";
break;
}
if (lastFocus) {
setTimeout(function () { lastFocus.focus() }, 10);
var textEdit = document.getElementById('textEdit');
var befSel = textEdit.value.substr(0, textEdit.selectionStart);
var aftSel = textEdit.value.substr(textEdit.selectionEnd, textEdit.length);
var select = textEdit.value.substring(textEdit.selectionStart, textEdit.selectionEnd);
textEdit.value = befSel + avS + select + apS + aftSel;
textEdit = textEdit.value
textEdit = BBcoder(textEdit);
document.getElementById('editorPreview').innerHTML = textEdit;
}
return (false);
});
This last part here triggers the preview event
$(document.body).on('blur', '#textEdit', function() {
lastFocus = this;
});
So i want it to come back to last focus but at a given position computed out of my selection + added bbcode length.
I have a class, that is supposed to display grey overlay over page and display text and loading gif. Code looks like this:
function LoadingIcon(imgSrc) {
var elem_loader = document.createElement('div'),
elem_messageSpan = document.createElement('span'),
loaderVisible = false;
elem_loader.style.position = 'absolute';
elem_loader.style.left = '0';
elem_loader.style.top = '0';
elem_loader.style.width = '100%';
elem_loader.style.height = '100%';
elem_loader.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
elem_loader.style.textAlign = 'center';
elem_loader.appendChild(elem_messageSpan);
elem_loader.innerHTML += '<br><img src="' + imgSrc + '">';
elem_messageSpan.style.backgroundColor = '#f00';
this.start = function () {
document.body.appendChild(elem_loader);
loaderVisible = true;
};
this.stop = function() {
if (loaderVisible) {
document.body.removeChild(elem_loader);
loaderVisible = false;
}
};
this.setText = function(text) {
elem_messageSpan.innerHTML = text;
};
this.getElems = function() {
return [elem_loader, elem_messageSpan];
};
}
Problem is, when I use setText method, it sets innerHTML of elem_messageSpan, but it doesn't reflect to the span, that was appended to elem_loader. You can use getElems method to find out what both elements contains.
Where is the problem? Because I don't see single reason why this shouldn't work.
EDIT:
I call it like this:
var xml = new CwgParser('cwg.geog.cz/cwg.xml'),
loader = new LoadingIcon('./ajax-loader.gif');
xml.ondataparse = function() {
loader.stop();
document.getElementById('cover').style.display = 'block';
};
loader.setText('Loading CWG list...');
loader.start();
xml.loadXML();
xml.loadXML() is function that usually takes 3 - 8 seconds to execute (based on download speed of client), thats why I'm displaying loading icon.