Play sound at each char with Typedjs

1.6k Views Asked by At

I want to make a realistic typing effect with sound. I'm using Typed.js, I have already create a simple example.

Here is my code :

var keystrokeSound = new Audio('http://www.freesfx.co.uk/rx2/mp3s/6/18660_1464810669.mp3');

function playSound () {
    keystrokeSound.pause();
    keystrokeSound.currentTime = 0;
    keystrokeSound.play();
}

var typed = new Typed('.element', {
    strings: ["Play sound each I type character", "It's only play on start of string"],
    typeSpeed: 50,
    preStringTyped : function(array, self){
        playSound();
    }
});
.typed-cursor{
  opacity: 1;
  animation: typedjsBlink 0.7s infinite;
  -webkit-animation: typedjsBlink 0.7s infinite;
          animation: typedjsBlink 0.7s infinite;
}
@keyframes typedjsBlink{
  50% { opacity: 0.0; }
}
@-webkit-keyframes typedjsBlink{
  0% { opacity: 1; }
  50% { opacity: 0.0; }
  100% { opacity: 1; }
}

.typed-fade-out{
  opacity: 0;
  transition: opacity .25s;
  -webkit-animation: 0;
          animation: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.6/typed.min.js"></script>
<span class="element" style="white-space:pre"></span>

The typing effect works as excepted. But no with the sound, the sound only play when the first char of sentence only. I have read the docs, but I can't found callback for each char typed. How to play sound on each typed char?

Here's the fiddle

5

There are 5 best solutions below

0
On BEST ANSWER

Answer my own question, the best way to do that is, edit the source code directly, and create a callback for each typed char. I'm just edit a little bit the source code (uncompressed version)

Found the typewrite function, and add this script before end of function

// fires callback function
this.options.onCharAppended(substr.charAt(0), this);

Also, found the defaults object, there is collection of default function for callback, I just add

onCharAppended: function onCharAppended(char, self){},

And then I can play sound with sync and precision time with the additional callback

var typed = new Typed('.element', {
    strings: ["Test ^1000 with delay"],
    typeSpeed: 100,
    onCharAppended : function(char, self){
        playSound();
    }
});
2
On

Try This code :

var keystrokeSound = new Audio('http://www.freesfx.co.uk/rx2/mp3s/6/18660_1464810669.mp3');
var interval ;
    function playSound () {
         clearInterval(interval);
         interval = setInterval(function(){
                 keystrokeSound.pause();
                 keystrokeSound.currentTime = 0;
                 keystrokeSound.play();
         },220);
    }

    var typed = new Typed('.element', {
        strings: ["Play sound each I type character", "It's only play on start of string"],
        typeSpeed: 100,
        preStringTyped : function(array, self){
            playSound();
        },
        onStringTyped : function(array, self){
            clearInterval(interval);
        },
        onComplete: function(array, self){
            clearInterval(interval);
        }
    });
.typed-cursor{
      opacity: 1;
      animation: typedjsBlink 0.7s infinite;
      -webkit-animation: typedjsBlink 0.7s infinite;
              animation: typedjsBlink 0.7s infinite;
    }
    @keyframes typedjsBlink{
      50% { opacity: 0.0; }
    }
    @-webkit-keyframes typedjsBlink{
      0% { opacity: 1; }
      50% { opacity: 0.0; }
      100% { opacity: 1; }
    }

    .typed-fade-out{
      opacity: 50;
      transition: opacity .25s;
      -webkit-animation: 0;
              animation: 0;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.6/typed.min.js"></script>
    <span class="element" style="white-space:pre"></span>

0
On

You could also try to loop the sound, and call the onStringTyped method from typed.js:

var keystrokeSound = new Audio('http://www.freesfx.co.uk/rx2/mp3s/6/18660_1464810669.mp3');

function playSound () {
    if (typeof keystrokeSound.loop == 'boolean')
    {
        keystrokeSound.loop = true;
    }
    else
    {
        keystrokeSound.addEventListener('ended', function() {
            this.currentTime = 0;
            this.play();
        }, false);
    }
    keystrokeSound.play();
}

function stopSound () {
    keystrokeSound.pause();
}

var typed = new Typed('.element', {
    strings: ["Play sound each I type character", "It's only play on start of string"],
    typeSpeed: 50,
    preStringTyped : function(array, self){
        playSound();
    },
    onStringTyped : function(array, self){
        stopSound();
    }    
});
.typed-cursor{
  opacity: 1;
  animation: typedjsBlink 0.7s infinite;
  -webkit-animation: typedjsBlink 0.7s infinite;
          animation: typedjsBlink 0.7s infinite;
}
@keyframes typedjsBlink{
  50% { opacity: 0.0; }
}
@-webkit-keyframes typedjsBlink{
  0% { opacity: 1; }
  50% { opacity: 0.0; }
  100% { opacity: 1; }
}

.typed-fade-out{
  opacity: 0;
  transition: opacity .25s;
  -webkit-animation: 0;
          animation: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.6/typed.min.js"></script>
<span class="element" style="white-space:pre"></span>

0
On

I guess the best would be to tweak the source code directly in order to make such an event available.

Here is the result of my best try from what we have now, using the Web Audio API in order to trigger the sound with the best timing precision possible, and ... It's not that fantastic...

For whatever reasons, the typing code doesn't seem to be very regular, I suspect perfs issues, but can't be sure, and didn't want to dissect the sources of the plugin yet.

Anyway, here is my attempt, if it can help you for the time being.

/* A simple player using the Web Audio API */
class Player {
  constructor(url) {
    this.media_url = url;
  }
  init() {
    this.ctx = new AudioContext();
    return fetch(this.media_url)
      .then(resp => resp.arrayBuffer())
      .then(buf => this.ctx.decodeAudioData(buf))
      .then(audioBuffer => this.audioBuffer = audioBuffer);
  }
  play() {
    const node = this.ctx.createBufferSource();
    node.buffer = this.audioBuffer;
    node.connect(this.ctx.destination);
    node.start(0);
  }
}

// I have absolutely no rights on the given sound so neither do any reader
// If authors have concerns about it, simply leave me a comment, I'll remove right away
const keystrokePlayer = new Player('https://dl.dropboxusercontent.com/s/hjx4xlxyx39uzv7/18660_1464810669.mp3');

function playSound (string, delay) {
  // It seems space is typed twice?
  const l = string.length + string.match(/\s/g).length;
  let current = 0;
  function loop() {
    // start our sound (1 time)
    keystrokePlayer.play();
    // if all have been played stop
    if(current++ >= l) return;
    // otherwise restart in 'delay' ms
    setTimeout(loop, delay);
  }
  loop();
}
// load our sound
keystrokePlayer.init().then(()=> {
  inp.disabled = false;
  btn.onclick();
});

btn.onclick = e => {
  var elem = makeNewElem();
  var typed = new Typed(elem, {
      strings: ["Play sound each I type character", "It's only play on start of string"],
      typeSpeed: +inp.value || 50,
      preStringTyped : function(index, self){
        const opts = self.options;
        playSound(opts.strings[index], opts.typeSpeed);
      }
  });
};
function makeNewElem(){
  var cont = document.createElement('div');
  var elem = document.createElement('span');
  elem.classList.add('element');
  cont.appendChild(elem);
  document.body.appendChild(cont);
  return elem;
}
.typed-cursor{
  opacity: 1;
  animation: typedjsBlink 0.7s infinite;
  -webkit-animation: typedjsBlink 0.7s infinite;
          animation: typedjsBlink 0.7s infinite;
}
@keyframes typedjsBlink{
  50% { opacity: 0.0; }
}
@-webkit-keyframes typedjsBlink{
  0% { opacity: 1; }
  50% { opacity: 0.0; }
  100% { opacity: 1; }
}

.typed-fade-out{
  opacity: 0;
  transition: opacity .25s;
  -webkit-animation: 0;
          animation: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.6/typed.min.js"></script>
<label>typeSpeed (ms)<input id="inp" type="number" value="50"><button id="btn">do it</button></label><br>

0
On

My solution was a bit different. I synced the audio to the typing speed using the audio's duration and playbackRate properties:

var typeSpeed = (audio.duration * 1000) / audio.playbackRate;

And then added the onComplete and the preStringTyped props to the option object:

var options = {
    typeSpeed: typeSpeed,
    preStringTyped: (arrPos) => {
         // Start the playing the looped audio when the typing's about to start
         audio.loop = true;
         audio.play();
    },
    onComplete: () => {
         // When the typing ends we stop the looping
         audio.loop = false;
         audio.stop();
    }
}

This lead to a convincing, albeit repetitive, typing sound effect.

Alternatively, if you wanted to more random effects, you could start the sound in the preStringTyped function and also leave a flag there in place of the audio.loop (e.g. repeat = true). Then, you would add a event listener for the onend event and play the audio again if the repeat flag is still on. Except this time you pick a random audio file to play. And lastly, in the onComplete callback you stop the currently playing audio and set the repeat flag to false.