SAVS plus protein (and scrolling)

Everything has protein now.

An extra 2 grams of protein or so in your cereal isn't really going to make a difference in your day anyway unless you eat the whole damn box. Not to mention, it seems the protein sources just tend to be whey or soy filler. I get quite a bit of protein per day (about a gram per pound of body weight) and while I don't have a problem with crap-free powders, I can get my protein from meat, fish, and some eggs (I don't eat dairy anymore and I don't actively look for soy in my diet, but I don't mind a little).

Anyway, last week I posted this about a new demo for SAVS, but I forgot that I kinda wanted (or used to want) the page to "keep up" with the audio so that the current text is visible on the page.

Now I'm thinking that is a separate thing than the basic sync between the audio and text because it easily makes assumptions about what the page should do. In other words, the "new" SAVS is a JavaScript library to sync stuff but it doesn't make UX decisions.

Anywho, I wanted a demo for how to add auto scrolling so I added some code for this demo here. You can play around with it if you want, or you can chug down a glass of protein water.

By the way, this seems to work OK on my desktop in current versions of Firefox, Chrome, and IE although in IE the line scrolled to flickers a little if it's already in view. I'm using IE 11, but this old snippet might offer a solution.

Update, October 7, 2015: Aw, screw it! Forget what I said about UX and separation of concerns. I added a built in scroll function but one now needs to manually start it up with "savs.initScroll()" as below in the revised JavaScript.

I also read that the "data-" attribute has issues with CAPS because of the strange way you have to use camelCase to reference hyphen-separated strings. So instead of using a "data-SAVS-start/stop" attribute I switched to "data-savs-start/stop". Whatever.

function SAVS(name) {
  var self = this;
  // Returns elements with class of "savs".
  _getList = function () {
    var cls = document.getElementsByClassName('savs');
    return cls;
  // Adds "onclick"" attribute to elements of class "savs".
  _setList = function () {
    var st2 = self.st2;
    for (i = 0; i < st2.length; i++) {
      fnc = + '.player.currentTime=' + st2[i].start;
      self.list[i].setAttribute('onclick', fnc)
  // Returns items in _getList() with start/stop properties and X/Y coordinates (if one uses a custom scrolling function and needs coordinates).
  _list2dict = function () {
    var dict = [];
    var list = self.list;
    for (i = 0; i < list.length; i++) {
      //start = list[i].getAttribute('data-savs-start'); //shouldn't use getAttribute for "data-" atributes, use .dataset per
      //stop = list[i].getAttribute('data-savs-stop');
      start = list[i].dataset.savsStart;
      stop = list[i].dataset.savsStop;
      box = list[i].getBoundingClientRect();
        'start': start,
        'stop': stop,
        'x': box.left,
    return dict;
  // Returns player element.
  _getPlayer = function () {
    return document.getElementById('savs-player');
  // Adds "ontimeupdate" attribute to player.
  _setPlayer = function () {
    var fnc = + '.setRegion(' + + '.getRegion(this.currentTime))';
    self.player.setAttribute('ontimeupdate', fnc);
  // Returns element index where "ss" (seconds) is within timed text element's start/stop times.
  self.getRegion = function (ss) {
    var st2 = self.st2
    for (i = 0; i < st2.length; i++) {
      if ((ss >= st2[i].start) && (ss < st2[i].stop)) {
        return i;
  // Updates class of current and previous timed text elements.
  self.setRegion = function (node) {
    if (typeof node != 'undefined') {
      if (node != self.currentNode) {
      self.currentNode = node;
    else {
  // Setup scrolling functionality (must explicitly be called at setup).
  self.initScroll = function () {
    self.scroll = true;
    // Scrolls to current line of timed text if scroll = true.
    self.scrollTo = function () {
      if (self.scroll == true) {
    // Add scrolling function to audio player's "ontimeupdate" attribute.
    var ontime = self.player.getAttribute('ontimeupdate');
    self.player.setAttribute('ontimeupdate', ontime + '; ' + + '.scrollTo()');
  // Setup.
  __init = function () { = name;
    self.currentNode = 0;
    self.list = _getList();
    self.st2 = _list2dict();
    self.player = _getPlayer();

var savs = new SAVS('savs');
savs.initScroll(); // add optional scrolling functionality.

Related Content:

Leave a Comment

Your email address will not be published. Required fields are marked *