I need to emulate what an old manual typewriter does when printing what is being typed on a web page. I want to develop JavaScript functions to pass it a string, and it would print out each character with a delay, and the sound file synced with each letter.
I'm new to JavaScript. What is the preferred method to do this? Should I be looking at jQuery for this? Or is this something simple to do?
I've seen problems with sound files being triggered like this on some web browsers, is there an audio file format which is best for this sort of thing?
I've found this, but the problem is, it doesn't work on all web browsers:
https://rawgit.com/mehaase/js-typewriter/master/example3-typewriter/index.html
You can try something like this:
// The delay between each keystroke
var delay = 300;
// The typewriter sound file url
var clickURL = "https://cdn.rawgit.com/halimb/res/6ffa798d/typewriter.wav";
// Get a reference to the container div
var container = document.getElementById("container");
var sampleString = "Hello world!";
//get a reference to the start button and typewrite onclick
var start = document.getElementById("btn");
start.onclick = function() { typewrite( sampleString ); };
function typewrite( str ) {
var i = 0;
container.innerHTML = "";
type();
function type() {
var click = new Audio( clickURL );
// This is triggered when the browser has enough of the file to play through
click.oncanplaythrough = function() {
click.play();
// Add the character to the container div
container.innerHTML += str[i];
i++;
if(i < str.length) {
window.setTimeout(type, delay);
}
}
}
}
* {
font-family: Courier;
font-size: 32px;
}
.btn {
display: inline-block;
border: 1px solid black;
border-radius: 5px;
padding: 10px;
cursor: pointer;
margin: 10px;
}
<div class="btn" id="btn">Start</div>
<div id="container"></div>
Update: on Safari. It seems the audio has to be triggered by a user event (e.g: onclick..), so I added a button, and made the typewriter start onclick.
The downside is that there's no way to pre-load the audio file, Safari make a server request and downloads the audio each time it is played. the only (dirty) way I could think of to overcome this is to provide a data URI instead of the audioURL.. you can try that if the playback speed really matters (this can be helpful: https://www.ibm.com/developerworks/library/wa-ioshtml5/)
Related
I am absolutely a noob when it comes to Javascript so I hope someone can help me please. I made a very simple Vanilla JS + HTML code that counts the number of times that it reaches 10 seconds (10 seconds = 1 count). This code will also refresh the page onmouseleave and when I change tab using window.onblur. My problem is that every time the page refreshes, the counter will go back to zero. What I want is that for the counter to deduct just one (or a specific number of) count every page refresh instead of completely restarting the count to zero. Please help me with Vanilla Javascript only and no JQuery (because I am planning to use this code personally and offline). Thank you in advance.
For those who may wonder what's this code is for, I want to create this to encourage myself to stay away from my computer for a certain period everyday. Like, if I can stay away from my computer for 100 counts, then I can use my computer freely after. I am addicted to the internet and I want to make this as my own personal way of building self-control.
Here is my code:
<style>
label {
color: orange;
}
p {
border-radius: 0px;
color: black;
text-decoration: none;
font-family: Consolas !important;
font-size: 16px;
font-weight: normal;
outline: none;
line-height: 0.25 ;
}
</style>
<body onmouseleave="window.location.reload(true)">
<p>You have earned <label id="pointscounter">00</label> point/s.</p>
<script>
var PointsLabel = document.getElementById("pointscounter");
var totalCountPoints = 0;
setInterval(setTimePoints, 10000);
function setTimePoints() {
++totalCountPoints;
PointsLabel.innerHTML = pad(totalCountPoints);
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
</script>
<script>
var blurred = false;
window.onblur = function() { blurred = true; };
window.onfocus = function() { blurred && (location.reload()); };
</script>
</body>
Storage
If you want the data to survive a reload, you need to save it somewhere. There are multiple options you can use. I used localStorage. You can learn more about it here: https://www.w3schools.com/jsref/prop_win_localstorage.asp. localStorage even survives closing the Browser Tab.
If you want to reset the data in a new session, you can use sessionStorage (just replace localStorage with sessionStorage): https://www.w3schools.com/jsref/prop_win_sessionstorage.asp.
What I did:
Save data on blur
If a blur-event occurs, the data is saved.
I also stopped the interval because there is no need for the interval anymore.
blurred variable
There is (currently?) no need for this variable.
The only usecase seems to be:
window.onfocus = function() {
blurred && location.reload();
};
To my knowledge you don't need this variable here.
Comming back
If the user already has points in localstorage, the current Points are calculated based on the points in localstorage. It currently deducts 1 point.
Using onmouseleave
I replaced the location.reload(true) on the body-tag with a function call. Everytime the mouse leaves, it calls this function. This function calls the onBlur function. The onBlur function is there, to ensure, that both window.onblur and onmouseleave do the same thing (save & stop). After the onBlur function is called, an EventListener is added to wait for mouseenter. When the mouse is seen again, we can reload the page with the onFocus function. It wouldn't reload the page as soon as the mouse left, because the timer would start (bc of reload), even if the mouse wasn't on the document.
Todo:
There is currently no check to see, if a the mouse in on the document after a reload. The timer will begin, even if the mouse isn't on the document.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
label {
color: orange;
}
p {
border-radius: 0px;
color: black;
text-decoration: none;
font-family: Consolas !important;
font-size: 16px;
font-weight: normal;
outline: none;
line-height: 0.25;
}
</style>
</head>
<body onmouseleave="mouseLeft()">
<p>You have earned <label id="pointscounter">00</label> point/s.</p>
<script>
var PointsLabel = document.getElementById("pointscounter");
var totalCountPoints = 0;
// Calculate Points if user has already collected points
if (localStorage.getItem("points") !== null) {
// You can change, how many points to deduct
const pointsToDeduct = 1;
var tempPoints = localStorage.getItem("points");
totalCountPoints = tempPoints - pointsToDeduct;
// Reset to 0 if points now negative
if (totalCountPoints < 0) {
totalCountPoints = 0;
}
PointsLabel.innerHTML = pad(totalCountPoints);
}
// need to save, to stop/clear it later
var timePointsInterval = setInterval(setTimePoints, 10000);
function setTimePoints() {
++totalCountPoints;
PointsLabel.innerHTML = pad(totalCountPoints);
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
function mouseLeft() {
onBlur();
document.addEventListener("mouseenter", onFocus);
}
function onBlur() {
// save Current Points:
localStorage.setItem("points", totalCountPoints);
//stop the timer
clearInterval(timePointsInterval);
}
function onFocus() {
location.reload();
}
// Blur Detection
var blurred = false;
window.onblur = function () {
// [-] blurred = true;
onBlur();
};
window.onfocus = function () {
// [-] blurred && location.reload();
onFocus();
};
</script>
</body>
</html>
i want to make shoutbox sound when shout is arrived. I've got a script and i styled button but i cant make script working.
script for sound is:
dvz_shoutbox.callbacks['update'].push(function(){
if ($('#shoutbox .entry.new').length) {
var audio = new Audio(rootpath + '/images/dvz_shoutbox.mp3');
audio.volume = 0.2;
audio.play();
}});
And it works,but i want to add on/of switch that will turn on and off this script, also i want to style it when it's on and off.
I got css look like this:
button.unmuted,button.muted {
vertical-align: middle;
padding: 0px 15px;
background: #060922;
line-height: 30px;
margin-top: -42px;
margin-left: 148px;
position: absolute;}
button.unmuted:hover {color:#c0c3bf}
button.muted:hover {color:#c0c3bf;}
button.muted:before{font-family: FontAwesome;content: "\f026";}
button.unmuted:before{font-family: FontAwesome;content: "\f028";}
Can anyone help me ??
Assuming that there is only one button in your HTML, you need to attach a click event handler to that button. The handler will toggle the classes muted and unmuted whenever the button is clicked.
Then, in your callback that creates the audio, you can check to see if your button contains the class muted. If it does, then do not play the sound.
Altogether, your code would look like so:
dvz_shoutbox.callbacks['update'].push(function(){
var $button = $("button");
if ($button.length && $button.hasClass("unmuted")) { // checck if unmuted
var audio = new Audio(rootpath + '/images/dvz_shoutbox.mp3');
audio.volume = 0.2;
audio.play();
}
});
$("button").click(function() { // adds click handler
var $this = $(this);
$this.toggleClass("muted");
$this.toggleClass("unmuted");
});
Note: in order for the code above to work, make sure your button starts out with class unmuted in your HTML.
I need to insert a huge html markup to some dom element which will take awhile. It is a reason why I want to display some preloader indicator. I have two blocks: #preloader and #container. Some code displays the preloader firstly and then starts to paste a big html markup.
The problem - preloader hasn't really displayed until browser will not finish render html markup. I've tried a lot of solutions (a lot of them are described here) but still haven't success.
An example is avalable below:
https://jsfiddle.net/f9f5atzu/
<div id='preloader'>Preloader...</div>
<div id='container'>Container...</div>
#preloader {
display: none;
background-color: #f00;
color: #fff;
hight: 100px;
text-align: center;
padding: 10px 20px;
}
#container {
background-color: #ccc;
}
setTimeout(function() {
// Define variables
let domPreloader = document.getElementById('preloader');
let domContainer = document.getElementById('container');
const html = Array(100000).fill("<div>1</div>");
// Display preloader
domPreloader.style.display = 'hide';
domPreloader.offsetHeight;
domPreloader.style.webkitTransform = 'scale(1)';
domPreloader.style.display = 'block';
// Render a big html
domContainer.innerHTML = html;
}, 1000);
Is there any solutions for the problem?
The way you did it, you're not releasing control to the browser between the display of the preloader and the display of the 'big html'.
Rather than encapsulating this whole block inside a setTimeout(), you should just differ the rendering part.
Please try something along those lines:
// Define variables
let domPreloader = document.getElementById('preloader');
let domContainer = document.getElementById('container');
// Display preloader
domPreloader.style.webkitTransform = 'scale(1)';
domPreloader.style.display = 'block';
// Render a big html
setTimeout(render, 100);
function render() {
const html = Array(100000).fill("<div>1</div>");
domContainer.innerHTML = html;
// Hide preloader
domPreloader.style.display = 'none';
}
JSFiddle
Meta: A similar question about locally stored A/V files can be found here: Clickable "positioning" hyperlinks to A/V (locally stored on your website and “hidden” behind a poster image).
Dear people from the Stackoverflow community,
The application
I am having an iframe <iframe name="video"... which is named video, and which is to be seen as the "main player" of a certain video.
Since I haven't been able to get interactive transcript on this video yet, I call different playing/starting positions in the video using: 1:00, e.g. for second 60.
This is working fine when the <iframe name="video".. is already "active": then the link shifts the video's playing position inside the iframe. This is great!
However it is not working fine, when the <iframe name="video".. isn't "active" yet, which is the case: then the link then opens in a different browser tab, instead of inside the iframe (or where the iframe is supposed to show up).
What I mean by hidden
What I mean with the iframe not being "active" is the following: it is "hidden" behind a "poster image" via the following code:
<div onclick="play();" id="vid" style="...; background: ... url('...poster.image.url...') no-repeat center;-webkit-background-size: cover; ...;overflow:hidden"></div>
<script type="text/javascript">function play(){document.getElementById('vid').innerHTML = '<iframe name="video" ... src="//www.youtube.com/embed/...?&...start=0"></iframe>';}</script>
In other words: i specifically don't want "<a target="_blank""-behaviour. I guess the target="video" is not working properly now, since the iframe is "hidden" behind the poster image.
I know for sure this behavior isn't occuring when the iframe wouldn't be hidden at all. I tested this multiple times. Further more, with the current "hidden" poster feature, this behavior is also not occuring when the poster image is clicked FIRST (before clicking on a <a href="...></a>).
If you would to see this behaviour for yourself, you can see it on my site. The best is to look/CTRL-F for "stef", and open the ▾̲ ̲u̲n̲d̲e̲r̲l̲i̲n̲e̲d̲ ̲t̲o̲g̲g̲l̲e̲, which you will find there.
So how to sucessfully "target" the "hidden" iframe without opening a new browser window/tab?
Any help would be greatly appreciated. Many thanks, Vincent Verheyen.
Well here we are! The fiddle.
HTML
Your html will contain both facades and jumpers to accomplish what you asked.
What's a facade?
facade: "the principal front of a building, that faces on to a street or open space."
This will be the image shown before you play the video or click on any jumpers, it's html will look like this:
<div class="videoFacade" data-name="video1" data-video="ByJFdTFEwF4" data-start="8">
<img src="http://i.imgur.com/xeUiWGn.png" />
</div>
What's a jumper?
A jumper is an anchor that will update the iframe's url to the desired time in the video, it will look like this:
JavaScript
window.addEventListener("load", initVideoFacade);
function initVideoFacade() {
var allFacades = document.querySelectorAll(".videoFacade");
for (var i = 0; i < allFacades.length; i++) {
var facade = allFacades[i];
setUpFacade(facade);
}
var allJumpers = document.querySelectorAll(".videoJumper");
for (var i = 0; i < allJumpers.length; i++) {
var jumper = allJumpers[i];
setUpJumper(jumper);
}
}
function setUpJumper(jumper) {
jumper.addEventListener("click", function (e) {
e.preventDefault();
jumpTo(jumper);
var video = jumper.dataset.video;
var facade = getFacadeByVideo(video);
if (facade) playVideo(facade);
return false;
});
}
function setUpFacade(facade) {
facade.addEventListener("click", function () {
playVideo(facade);
});
}
function getFacadeByVideo(video) {
return document.querySelector(".videoFacade[data-name=" + video + "]");
}
function getIframeByVideo(video) {
return document.querySelector(".videoIframe[data-name=" + video + "]");
}
function updateVideoSource(iframe, start, end) {
var iframeSrc = iframe.src.replace(/start=[0-9]+/i, "start=" + start);
var hasEnd = iframeSrc.indexOf("end") != -1;
if (hasEnd) iframeSrc = iframeSrc.replace(/end=[0-9]+/i, "end=" + end);
else iframeSrc += "&end=" + end;
return iframeSrc;
}
function updateFacadeData(facade, start, end) {
facade.setAttribute("data-start", start);
facade.setAttribute("data-end", end);
}
function jumpTo(jumper) {
var start = jumper.dataset.start;
var end = jumper.dataset.end;
var video = jumper.dataset.video;
var iframe = getIframeByVideo(video);
if (iframe) {
var iframeSrc = updateVideoSource(iframe, start, end);
iframe.src = iframeSrc;
} else {
var facade = getFacadeByVideo(video);
updateFacadeData(facade, start, end);
}
}
function playVideo(facade) {
var start = facade.dataset.start || 0;
var end = facade.dataset.end;
var name = facade.dataset.name;
var video = facade.dataset.video;
var iframe = document.createElement("iframe");
iframe.dataset.name = name;
iframe.className = "videoIframe";
var iframeSrc = "http://www.youtube.com/embed/" + video + "?&cc_load_policy=1&modestbranding=1&autoplay=1&rel=0&showinfo=0&theme=light&start=" + start;
if (end) iframeSrc += "&end=" + end;
iframe.src = iframeSrc;
iframe.frameBorder = 0;
replaceNode(facade, iframe);
}
function replaceNode(node1, node2) {
var parent = node1.parentNode;
var next = node1.nextSibling;
parent.insertBefore(node2, next);
parent.removeChild(node1);
}
Here is a timeline:
We add the initVideoFacade() method to the load event in the page, this will make sure all our facades and jumpers are up and running before doing anything.
The initVideoFacade() method will find all jumpers and facades and set them up using the setUpFacade() and setUpJumper() methods.
The setUpJumper() method will add a click event on the jumper and tell it to jump to a determined time in the video, specified in the jumper. Also, if the video is not yet playing, it will do so now.
The jumpTo() method will update the iframe's src (or the facade initial data if the video is not playing) using a couple of regular expressions to replace the &start= and &end= parts of the iframe src.
The setUpFacade() method will simply play the video, removing the facade and inserting the iframe.
The playVideo() method will create a new iframe from a facade, replacing it and assigning it's source, start and end time to the video.
CSS
This just handles the styling of the facade and iframe :)
.videoFacade, .videoIframe {
position: relative;
width: 360px;
height: 202.5px;
margin:5px;
}
.videoFacade {
cursor: pointer;
border:1px solid black;
}
.videoFacade img {
position: absolute;
width: 50px;
height: 50px;
left: 50%;
top: 50%;
margin: -25px 0 0 -25px;
}
Hope it helped!
I am trying to get a sound file to play faster in order to keep up with the text reveal in the following code. The sound works (in Firefox, anyway), but it only seems to play one instance of the file at a time.
I'd like to have each letter pop onto the screen accompanied by the popping sound. Right now the popping sound is sort of random, and not timed to play with each letter.
I'm wondering if I need to have multiple instances of the sound object, and how to do that.
I already shortened the sound file as much as I could, and the length of the file is shorter than the setTimeout interval I'm using. It just won't overlap multiple copies of the same sound file, for some very good reason that I don't know, I'm sure.
Here is the whole code:
(I tried to JSFiddle it, but couldn't get that to work (I'll save that question for a later date))
<html>
<head>
<style>
#display {
color: white;
font-size: 150%;
padding: 2em 5em;
min-height: 600px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
body {
background-color: black;
padding: 0;
margin:0;
}
</style>
<script>
var text = "Test String... 1, 2, 3; Everything seems to be working, but the sound is lagging.";
var charDelay = 40; // Sets the delay time for the character loop
function loadText() {
var i = 0; // Character counter
var myPud = new Audio("http://southernsolutions.us/audio/pud03.ogg");
var displayBox = document.getElementById("display");
displayBox.innerHTML = "<p>";
textLoop();
function textLoop() {
if (i == text.length){ // This condition terminates the loop
displayBox.innerHTML += "</p>";
return;
} else if (i < text.length) { // This condition appends the next character
displayBox.innerHTML += text[i];
i++;
myPud.play();
setTimeout(function(){return textLoop()}, charDelay);
}
}
}
window.onload = function(){loadText()};
</script>
</head>
<body>
<div id="display"></div>
</body>
</html>
I'd suggest that you make the sound loop a little longer, say 1 second. Then, control the sound playing through event listeners so that once the text has finished, you stop the sound playing.
Trying to do it as you're doing it now, you could speed up the sound with how its playing in the audio file. This should give better results. That, or slow down the time out.
Below is some code that I've tested which would let you do it through event listeners. The results are similar to what you had, but if you took your audio file, increased it to 1 second and changed it up to have 24 clicks in there, you'd get the exact effect you were looking for.
Edit: I've also updated the below to take into account the comments.
<script>
var text = "Test String... 1, 2, 3; Everything seems to be working, but the sound is lagging.";
var charDelay = 40; // Sets the delay time for the character loop
function loadText() {
var i = 0; // Character counter
var myPud = new Audio("http://southernsolutions.us/audio/pud03.ogg");
var displayBox = document.getElementById("display");
// Toggle for whether to loop
var stillPlay = true;
displayBox.innerHTML = "<p>";
// Listen for when it ends
myPud.addEventListener("ended", onAudioComplete);
// Begin playing
myPud.play();
// Start the loop
textLoop();
function textLoop() {
if (i == text.length){ // This condition terminates the loop
displayBox.innerHTML += "</p>";
// If we're at the end, we want to stop playing
stillPlay = false;
// Rather than duplicate code, jump straight into the complete function
onAudioComplete(null);
return;
} else if (i < text.length) { // This condition appends the next character
displayBox.innerHTML += text[i];
i++;
// Direct reference to the function to avoid more anony. functions
setTimeout(textLoop, charDelay);
}
}
// On audio complete
function onAudioComplete(e){
// Can we still play? If so, play
if(stillPlay){
myPud.play();
} else {
// Otherwise, remove the event listener, stop and null out.
myPud.removeEventListener("ended", onAudioComplete);
myPud.stop();
myPud = null;
}
}
}
window.onload = loadText;
</script>