1
\$\begingroup\$

I would like someone to review my code and maybe tell me better ways to go about doing certain aspects.

I will try my best to breakdown the code and explain what is happening, there is quite a bit of code, please see the CodePen here to see it in action and to also see all the code together.

Note: They are some polyfills at the top of the code in the CodePen, simply disregard them until you get the class.

So i'm using class's to create the plugin, below is the general structure:

// Plugin Start
class LightBox {

   constructor(node) {
      const _ = this;

      Object.assign(this, {
         node: node,
         label: node.dataset.lightboxName,
         group: node.dataset.lightboxGroup,
         enabled: true,
         id: 'lightbox',

         activeClass: 'lightboxActive', // Add this class to the body whilst active
         appendTo: '#main-wrap', // Append the lightbox to this element
         debug: false,

         // Visual settings
         showTitle: true,
         showMax: true,
         showThumbs: true,

         // Store variables
         count: 0, // Count images
         active: false, // Is lightbox active?

         autoplay: {
            delay: 3000,
            timer: null,
            enabled: true,
            initiallyDisabled: true
         },

         current: null, // Current image index
         last: 0, // Last image index

         zoom: {
            state: 0,
            levels: {
               0: 1,
               1: 1.5,
               2: 2,
               3: 2.5
            },
            enabled: null
         },

         // Errors occured?
         error: false,
         lightboxHTML: 'JSONCode',
         thumbnailsHTML: 'JSONCode',
         galleryHTML: 'JSONCode',
      });
   }
   static init(node) {
      return new this(node);
   }
   static initAll(node) {
      var _ = this,
         instances = [];

      window.addEventListener('load', function () {
         var groups = document.querySelectorAll('[data-lightbox-group]');
         var current = [];
         var handleGroup = function (e) {
            instances.push(_.init(e)); // Setup new instance of this class for each group
         };

         // Establish unique group names and create new instances
         for (var i = 0; i < groups.length; i++) {
            var dataset = groups[i].dataset;
            if (current.indexOf(dataset.lightboxGroup) === -1) {
               current.push(dataset.lightboxGroup);
               handleGroup(groups[i]);
            }
         }
      });
      return instances;
   }
}
const lightbox = LightBox.initAll();

I have remove some of the code for now, just going to explain what the above is doing.

It should be pretty obvious really.

  1. Assigning some properties to the instance of the class.
  2. Using initAll i'm then going to search the DOM for an instances of [data-lightbox-group as I want unique instances of the lightbox for each group.
  3. Creating a new instance of the LightBox class within the init(node) method, this way any values used during the runtime of my lightbox will be unique to the instance.

I create the HTML for my lightbox by converting some JSON code to HTML, not included this as some is quite lengthy and I don't think it's that important.

// Plugin Start
class LightBox {

  constructor(node) {
    const _ = this;

    Object.assign(this, {
       /* values here seen above */
    });

    // We will use the group value to only get the images within this instance of the lightbox
    _.images = document.querySelectorAll('[data-lightbox-group=' + _.group + ']');

    // Replace bracelets
    _.lightboxHTML = this.lightboxHTML.replace(/\{(name)}/g, _.id);
    _.thumbnailsHTML = this.thumbnailsHTML.replace(/\{(name)}/g, _.id);
    _.galleryHTML = this.galleryHTML.replace(/\{(name)}/g, _.id);
    _.galleryHTML = this.galleryHTML.replace(/\{(group)}/g, _.label);

    // Lets start to setup the lightbox, only continue if no fatal errors occured

    if (!this.error) this.initEvents.call(this); // Setup object with lightbox update functions
    if (!this.error) this.initHTML.call(this); // Generate the HTML for the lightbox

    (function() {
      var compile = [];

      for(var i = 0; i < _.images.length; i++) {
        _.images[i].classList.add('addedLightbox'); // Add some reference to the image that it has been added, styling?

        var tmp = {
          index: _.count, // We want a unique index for each image
          object: _.images[i], // Store a reference to the image
          src: function () { // Establish if a different image should be used when viewed in the lightbox
            if (_.images[i].hasAttribute('data-lightbox-large')) return _.images[i].getAttribute('data-lightbox-large');
            return _.images[i].src;
          }(),
          active: { val: 0 },
          alt: function () {
            if (_.images[i].hasAttribute('alt')) return _.images[i].getAttribute('alt');
            return '';
          }()
        }
        compile.push(tmp);
        _.count++; // Increase max number for each image
      }
      _.images = compile;
    }());

    if (!this.error) this.initThumbnails.call(this, _.images);
    if (!this.error) this.openLightbox.call(this, _.images);

    if (!this.error) this.initKeyboard.call(this); // Assign events to keyboard events
    if (!this.error) this.initTouch.call(this); // Assign events to touch screen events

    // Add initial click events
    var addClickEvent = function(index) {
      var image = _.images[index];
      (image.object).addEventListener('click', function(event) {
        event.stopPropagation();
        _.openLightbox.call(_, image.index);
      });
    }
    for (var i = 0; i < _.images.length; i++) {
      addClickEvent(i);
    }

    /**
     *  Public Methods
     */
    _.public = function () {
      var a = arguments[0],
        b = arguments[1];
      switch (a) {
        // Actions
        case 'left':
          _.events.EventLeft();
          break;
        case 'right':
          _.events.EventRight();
          break;
        case 'slide':
          _.events.EventSlide((b - 1));
          break;
        case 'close':
          _.events.EventClose();
          break;
        case 'pause':
          _.events.EventPause();
          break;

          // Return Values
        case 'get':
          return _.events.ReturnVal(b);
      }
    }
  }

Below the Object.assign I will assign some more properties to the object however I need to use existing properties here, so I can't put them within the Object.assign as they won't exist until that chunk of code has finished running.

  1. _.images will compile a list of images for the group of the current instance of the class.
  2. Replace bracelets within my HTML code.
  3. I will break down each of the other methods, but as you can see on the lines starting with if (!this.error) i'm going to be running methods.
  4. The anonymous function in between the method calls compiles an array of objects which contain the required information I need about an image and it's state (state got made abundant towards the end of this so could probably remove it).
  5. The below code snippet which is also above simply assigns the initial click events to the thumbnails.

    var addClickEvent = function(index) {
       var image = _.images[index];
       (image.object).addEventListener('click', function(event) {
          event.stopPropagation();
          _.openLightbox.call(_, image.index);
       });
    }
    for (var i = 0; i < _.images.length; i++) {
       addClickEvent(i);
    }
    
  6. The _.public property is simply going to open up some of the lightbox's functions to external sources.


Below is the structure of my class with the method names in, I will empty the methods and go through them 1 by 1.

// Plugin Start
class LightBox {

  constructor(node) {
    const _ = this;

    Object.assign(this, {


    });
  }

  /**
   *  Handle plugin for each group
   */
  initEvents() {}
  changeImage(index) {}
  manageZoom(zoom) {}
  activeClasses(index) {}
  initHTML() {}
  initThumbnails(list /* list = { index, object, src, active } */ ) {}
  openLightbox(index) {}
  initAutoplay() {}
  initKeyboard() {}
  initTouch() {}
  updateThumbnailPadding(index) {}
}
const lightbox = LightBox.initAll();

---------------------- initEvents() ----------------------

With the below code I try and keep any action performed on the lightbox within here, so it only needs to be changed in 1 place.

Most of the properties should make sense, but I will break them down:

EventRight: This change the current image to the next one in the list, if it's at the end then it will go to the first one. I will show the _.changeImage method after the below code.

EventLeft: Same thing as above but go to the previous image.

EventClose: This will close the lightbox, the closing happens within _.openLightbox which I will get to. Whilst writing this I realise that the loop containing _.images[i].active.val = 0; isn't actually needed anymore, I was using some ES6 to watch the value of active and when this changes run some code, but the polyfill was too many lines of code for my liking so I moved away from this.

EventSlide: This is similar to left/right except it will change the image to the given index.

EventClass: Within my JSON I have made it simple to add event triggers, incase someone want's to modify the HTML. So this method will simply check to see what action is to be taken by extracting the information from a string, for example:

EventClass(#elementId, toggle: hover-left)

So the EventClass method would take the above and chose one of the switch options, so in this case it will add a class and then remove it on mouseout. The event listener for this is added during the conversion from JSON to HTML.

ReturnVal: Simply allows me to return certain values during runtime for external uses or simply debugging.

EventGallery: This method will display all the images in a grid view, see the example in the link at the top of the page.

  1. Generate the HTML from JSON for this section.
  2. Duplicate the image elements as I need to append the same images to another element, I don't want to move the original thumbnails from where they are on the website.
  3. Assign click events to the images, if one is clicked then this page needs to be removed and the current lightbox image needs to be changed to what was clicked.
  4. I pause the autoplay feature whilst this is active.

EventGalleryClose: Pretty self explanatory.

EventZoomIn: Allows the user to zoomin on the current image, goes to external method which will be displayed later.

EventZoomOut & EventZoomReset: Same as above.

EventPause: This simply pauses the autoplay feature, sets the master value to true to stop the autoplay from being unpaused from other actions.

Zooming in on an image pauses the autoplay, zooming back out will resume the autoplay, so the master value was required to stop this if the main pause button was clicked.

EventPlay: Same as above but resumes the autoplay.

initHTML:

initEvents() {
    var _ = this;

    _.events = {
      EventRight: function (evt) {
        if (!_.galleryActive) {
          var index = 0;
          if (evt) evt.stopPropagation();

          index = _.current + 1;
          if (_.current === (_.count - 1)) index = 0;

          _.changeImage.call(_, index); // Update lightbox

        }
      },
      EventLeft: function (evt) {
        if (!_.galleryActive) {
          var index = _.count;
          if (evt) evt.stopPropagation();

          // Previous image
          index = _.current - 1;
          if (_.current <= 0) index = (_.count - 1);

          _.changeImage.call(_, index); // Update lightbox
        }
      },
      EventClose: function (evt) {
        if (evt) evt.stopPropagation();

        _.openLightbox.call(_, null);

        // Make sure all active classes are removed
        for (var i = 0; i < _.images.length; i++) {
          _.images[i].active.val = 0;
        }
      },
      EventSlide: function (number) {
        if (_.images[number] !== undefined) {
          _.changeImage(number);

        } else {
          // No such image...
        }
      },
      EventClass: function (evt) {
        try {
          var arg = (evt.target.dataset.hover).replace(/ /g, ''),
            args = (arg.match(/[^(]*/g).filter(Boolean))[1].match(/(?:([^,()]+)?)+/g).filter(Boolean),
            target = args[0].objectType(),
            action = args[1].match(/^(.*):(.*)/).filter(Boolean);

          switch (action[1]) {
            case 'add':
              // Add class
              target.classList.add(action[2]);
              break;
            case 'remove':
              // Remove class
              target.classList.remove(action[2]);
              break;
            case 'toggle':
              target.classList.add(action[2]);
              evt.target.addEventListener('mouseout', function () {
                target.classList.remove(action[2]);
              });
              break;
            default:
              // action doesn't exist, log error
              break;
          }
        } catch (e) {
          // error
        }
      },
      ReturnVal: function (e) {
        var val;
        switch (e) {
          case 'count':
            val = _.count;
            break;
          case 'active':
            val = _.active;
            break;
          case 'current':
            val = _.current;
            break;
          case 'last':
            val = _.last;
            break;
          case 'images':
            val = _.images;
            break;
          case 'html':
            val = _.lightbox;
            break;
          default:
            val = _;
            break;
        }
        return val;
      },
      EventGallery: function () {
        _.galleryActive = true;
        if (_.autoplay.current !== undefined) _.autoplay.current.pause();
        _.gallery = _.HTMLfromJSON(_.galleryHTML);

        // Should duplicate the image reference, but keep reference to the active state
        var newList = [];
        var createImage = function (image, i) {

          // Create new instances of the images
          var img = document.createElement('img');
          img.src = image.object.src;
          img.classList.add('responsive-img');
          img.alt = image.alt;

          var span = document.createElement('span');
          span.appendChild(img);

          if (i === _.current) img.parentNode.classList.add('active');

          var newImage = {
            object: img,
            index: i,
            src: image.src,
            active: image.active, // Use reference to old list
            alt: image.alt
          };
          newList.push(newImage);

          newImage.object.addEventListener('click', function (evt) {

            evt.stopPropagation();
            _.changeImage.call(_, newImage.index);
            _.galleryActive = false;
            (_.gallery.object).parentNode.removeChild(_.gallery.object);

            if (_.autoplay.current !== undefined) _.autoplay.current.resume();

          });

          // Add each image to the gallery DOM
          _.gallery.main.appendChild(span);
        }

        for (var i = 0; i < _.images.length; i++) {
          createImage(_.images[i], i);
        }

        // Add the gallery to the DOM
        _.lightbox.object.appendChild(_.gallery.object);

      },
      EventGalleryClose: function (evt) {
        evt.stopPropagation();
        (_.gallery.object).parentNode.removeChild(_.gallery.object);
        _.galleryActive = false;
      },
      EventZoomIn: function(evt) {
        if (evt) evt.stopPropagation();
        _.manageZoom(1);
      },
      EventZoomOut: function(evt) {
        if (evt) evt.stopPropagation();
        _.manageZoom(0);
      },
      EventZoomReset: function(evt) {
        if (evt) evt.stopPropagation();
        _.manageZoom(null);
      },
      EventPause: function(evt) {
        if (evt) evt.stopPropagation();
        _.autoplay.current.master(true);
        _.autoplay.current.pause();
      },
      EventPlay: function(evt) {
        if (evt) evt.stopPropagation();
        _.autoplay.current.master(false);
        _.autoplay.current.resume();
      }
    };
  }



changeImage(index) {
    const _ = this;

    _.lightbox.counter.innerHTML = (index + 1) + '/' + _.images.length;
    _.lightbox.image.src = _.images[index].src;
    _.lightbox.image.alt = _.images[index].alt;
    _.lightbox.title.innerHTML = _.images[index].alt;

    _.last = (_.current === null) ? index : _.current;
    _.current = index;

    _.images[index].active.val = 1;
    _.images[_.last].active.val = 0;

    // Move thumbnails
    if (_.showThumbs) _.updateThumbnailPadding(index);

    // Add/Remove active classes
    _.activeClasses(index);

    // Reset the zoom state if image is changed
    _.manageZoom(null);
  }

---------------------- changeImage() ----------------------

This is the main method which changes the current displayed image.

  1. Change the elements values, such as the main image src, innerHTML of some Elements.
  2. Change the current and last values. (_.current === null) ? index : _.current was needed for when the lightbox was first opened so last wasn't null.
  3. Again could probably remove active.val.
  4. If the thumbnails at the bottom of the lightbox are visible we need to update the offset, so the active image is always in the middle of the screen.
  5. if the image is changed then we need to zoom out.

    changeImage(index) {
       const _ = this;
       _.lightbox.counter.innerHTML = (index + 1) + '/' + _.images.length;
       _.lightbox.image.src = _.images[index].src;
       _.lightbox.image.alt = _.images[index].alt;
       _.lightbox.title.innerHTML = _.images[index].alt;
    
       _.last = (_.current === null) ? index : _.current;
       _.current = index;
    
       _.images[index].active.val = 1;
       _.images[_.last].active.val = 0;
    
       // Move thumbnails
       if (_.showThumbs) _.updateThumbnailPadding(index);
    
       // Add/Remove active classes
       _.activeClasses(index);
    
       // Reset the zoom state if image is changed
       _.manageZoom(null);
    }
    

---------------------- manageZoom() ----------------------

Feels a bit code heavy for my liking for what it does. The zoom states available are in the settings at the top of this post. This method simply uses these to change the scale() value, but we also need to check to see if we hit the maximum zoom available, if so add some classes to visually display this.

Obviously then do the same thing when zooming out or resetting the zoom. And of course pausing and resuming the autoplay.

manageZoom(zoom) {
  const _ = this;

  switch(zoom) {
    case 1:
    // Zoom in
    if (_.zoom.levels[(_.zoom.state + 1)] !== undefined) {
      _.zoom.state = _.zoom.state + 1;
      _.lightbox.imageContainer.style.transform = "scale(" + _.zoom.levels[_.zoom.state] + ")";
    }
    break;
    case 0:
    // Zoom out
    if (_.zoom.levels[(_.zoom.state - 1)] !== undefined) {
      _.zoom.state = _.zoom.state - 1;
      _.lightbox.imageContainer.style.transform = "scale(" + _.zoom.levels[_.zoom.state] + ")";
    }
    break;
    case null:
    // Reset
      _.zoom.state = 0; _.zoom.enabled = null;
      _.lightbox.imageContainer.style.transform = "scale(1)";
      _.lightbox.zoomin.classList.remove('disabled');
      _.lightbox.zoomout.classList.add('disabled');
    break;
  }

  if (_.zoom.levels[(_.zoom.state + 1)] === undefined) {
    _.lightbox.zoomin.classList.add('disabled');
  } else {
    _.lightbox.zoomin.classList.remove('disabled');
  }

  if (_.zoom.levels[(_.zoom.state - 1)] === undefined) {
    _.lightbox.zoomout.classList.add('disabled');
    if (_.autoplay.current !== undefined) _.autoplay.current.resume();
  } else {
    _.lightbox.zoomout.classList.remove('disabled');
    if (_.autoplay.current !== undefined) _.autoplay.current.pause();
  }

}

---------------------- activeClasses() ----------------------

I like adding classes to active images incase the user want's to style these for some reason. So this method will simply add and remove these classes to both the original thumbnails and the thumbnails along the bottom of the lightbox.

activeClasses(index) {
  const _ = this;

  if (index === null) {
    _.images[_.current].object.classList.remove('active');
    _.imagesThumbnails[_.current].object.parentNode.classList.remove('active');

    return;
  }

  _.images[_.last].object.classList.remove('active');
  _.images[index].object.classList.add('active');

  // Add class to the span
  _.imagesThumbnails[_.last].object.parentNode.classList.remove('active');
  _.imagesThumbnails[index].object.parentNode.classList.add('active');
}

---------------------- HTMLfromJSON() ----------------------

Quite a bit happens here, this is where our HTML is managed, not actually generated here, this is an eternal prototype function which we will get to.

  1. eventName is used to extract the event type from the dataset values.
  2. Use the external prototype to generate the HTML from JSON code.
  3. This is where the eventListeners for the EventClass is assigned, so if we find data-hover dataset then we add the mouseover eventListener and of course assign the required event and function to it from the EventClass method.
  4. I try and keep an object with references to main HTMLElements for all parts of the plugin, this removes the need to add unique classes/ID's to reference if I need to manipulate this HTML.

    HTMLfromJSON(html) {
        var _ = this,
           result;
    
        // Filter the event name, this is in the data attribute value
        var eventName = function (e) {
           return e.match(/[^(]*/);
        };
    
        try {
           // Try and generate the HTML from the JSON
           result = html.generateHTMLfromJSON();
        } catch (e) {
           console.log('Lightbox Error (Generating HTML): ' + e);
        }
    
        // Check that the HTML was generated
        if (result[0]) {
    
           // Assigned mouse events to the HTML based on data attributes, 
           // this will assign the lightbox events functions.
           Array.prototype.forEach.call(result[1].querySelectorAll('[data-hover]'), function (e) {
              e.addEventListener('mouseover', function (event) {
              _.events[eventName(e.dataset.hover)](event);
              });
           });
           Array.prototype.forEach.call(result[1].querySelectorAll('[data-click]'), function (e) {
              e.addEventListener('click', function (event) {
              _.events[eventName(e.dataset.click)](event);
              });
           });
    
           // Store a reference to key elements with the HTML and
           // store the main HTML object, allow access to this references to
           // extended classes for updates and bolt-ons. 
           var object = {};
           object['object'] = result[1];
           Array.prototype.forEach.call(result[1].querySelectorAll('[data-name]'), function (e) {
              object[e.dataset.name] = e;
           });
    
        } else {
           _.error = true; // HTML didn't generate
           console.log('error generating html: ' + result[1]);
        }
    
        // Return the HTML
        return object;
     }
    

---------------------- initHTML() ----------------------

This is a simple method, we just initiate the main HTML for the lightbox here and add it to a property. We also check if there is more than 1 image, if there isn't we add a class as some controls can't really be used.

initHTML() {
  var _ = this;

  // Use JSON to generate the HTML, this allows the HTML structure to be changes
  _.lightbox = _.HTMLfromJSON(_.lightboxHTML);

  // If there is only 1 image add a class, some HTMLElement's may need to be hidden.
  if (_.images.length <= 1) {
    _.lightbox.container.classList.add(_.id + 'Slider--hide-controls');
  }
}

---------------------- initThumbnails() ----------------------

We need to duplicate the main image list here again as to have unique images and not move the current ones.

  1. First generate the HTML from the JSON.
  2. Now duplicate the images again, however we need to put the images within an element, I just use span's here.
  3. We want to have another array of these new objects created like we do with the main images.
  4. Now just append the images to their wrapper.
  5. Add click events to the images.
  6. Append the wrapper to the lightbox.
  7. Keep reference to this wrapper with the main lightbox object.

    initThumbnails(list /* list = { index, object, src, active } */ ) {
      const _ = this;
    
      _.thumbnails = _.HTMLfromJSON(_.thumbnailsHTML); // Generate the HTML
    
      // Create a div to wrap the images in
      var wrapper = document.createElement('div');
      wrapper.classList.add('thumbnails');
    
      var newList = [];
      _.imagesThumbnails = [];
    
      for (var i = 0; i < _.images.length; i++) {
        var img = document.createElement('img');
        img.src = _.images[i].object.src;
        img.alt = _.images[i].alt;
    
        newList.push({
          object: img,
          index: i,
          src: _.images[i].src,
          alt: _.images[i].alt,
          active: _.images[i].active
        });
    
        var container = document.createElement('span');
        container.appendChild(newList[i].object);
    
        // Keep a reference to the thumbnails
        _.imagesThumbnails.push(newList[i]);
    
        // Append image to wrapper
        wrapper.appendChild(container);
      }
    
      _.addClickEvents(_.imagesThumbnails);
      _.lightbox.bottom.appendChild(wrapper);
      _.lightbox.thumbnailWrapper = wrapper;
    
    }
    

---------------------- openLightbox() ----------------------

There are two parts to this method really, one closes the lightbox and the other closes it.

Closing the lightbox will simply pass null into the the method call, we then wait for a transition to finish then we:

  1. Remove the lightbox from the DOM.
  2. Reset classes by removing state classes, such as remove and the active class from the body.
  3. Reset the zoom values and active classes from the thumbnails, as these still exist in memory.
  4. Stop the autoplay.

Opening works in a similar fashion to above:

  1. Setup the current and last properties.
  2. Append the HTML to the DOM.
  3. Add active class to the body.
  4. We do an IE check and add a class to the body as IE9 has issues, well, IE in general...
  5. Start the autoplay if it's enabled, and if there is more than 1 image in the group.
  6. Finally we change the image to the image which was clicked.

    openLightbox(index) {
      const _ = this;
    
      // If null is passed then close the lightbox
      if (index === null) {
    
        const transitionEvent = whichAnimationEvent();
    
        if (transitionEvent === undefined) {
          removeLightbox();
        }
    
        _.lightbox.object.classList.add("remove");
        _.lightbox.object.addEventListener(transitionEvent, removeLightbox);
    
        function removeLightbox(event) {
          if (transitionEvent !== undefined) {
            _.lightbox.object.removeEventListener(transitionEvent, removeLightbox);
          }
          _.lightbox.object.classList.remove("remove");
          if (_.lightbox.object) _.lightbox.object.parentNode.removeChild(_.lightbox.object);
    
          document.body.classList.remove(_.activeClass);
    
          _.activeClasses(null);
          _.manageZoom(null);
    
          _.active = false;
          _.current = null;
    
          if (_.autoplay.current !== undefined) {
            _.autoplay.current.stop();
            _.autoplay.current.master(true);
          }
        }
    
      } else {
    
        if (_.images[index] !== undefined) {
    
          _.last = (_.current === null) ? index : _.current;
          _.current = index;
          _.active = true;
    
          // Append the HTML if it doesn't yet exist
          if (('#lightbox').objectType() === null) {
    
            (_.appendTo).objectType().insertBefore(_.lightbox.object, ((_.appendTo).objectType()).firstChild);
            document.body.classList.add(_.activeClass);
            if (navigator.appVersion.indexOf("MSIE 9") > -1) document.body.classList.add('ie9');
    
            // Change the active image values
            _.images[_.last].active.val = 0;
            _.images[index].active.val = 1;
    
            if (_.autoplay.enabled && _.images.length > 1) _.initAutoplay.call(_);
          }
    
          _.events.EventSlide(index);
    
        } else {
          // Image doesn't exist
        }
      }
    }
    

---------------------- initAutoplay() ----------------------

This code is pretty straight forward as it doesn't refer to any other parts of the plugin really:

  1. We create an element to use as a visual loading bar, obviously this bar will reach the end of the window and then the next image will come in. To make this animation to reply again we just remove it and then re-add it to the DOM.
  2. togglePauseState simply pauses the animation and adds/removes class's on the controls.

    initAutoplay() {
    
      var _ = this, delayElm, newone;
    
      if (document.getElementById('nextChange')) {
        delayElm = document.getElementById('nextChange');
      } else {
        delayElm = document.createElement('div'), newone;
        delayElm.classList.add('nextChange');
        delayElm.id = 'nextChange';
        delayElm.style.animation = 'nextChange forwards ' + (_.autoplay.delay / 1000) + 's ease-in-out';
        delayElm.style.animationPlayState = 'paused';
        _.lightbox.container.insertBefore(delayElm, _.lightbox.container.firstChild);
      }
    
      _.autoplay.timer = function(callback, delay) {
        var timerId, start, remaining = delay, 
            paused = true, master = _.autoplay.initiallyDisabled;
    
        this.master = function(val) {
          master = val;
        }
    
        this.pause = function() {
          if (!paused) {
            window.clearTimeout(timerId);
            remaining -= new Date() - start;
            paused = true;
    
            this.togglePauseState();
          }
        };
    
        this.resume = function() {
          if (paused && !master) {
            start = new Date();
            timerId = window.setTimeout(callback, remaining);
            paused = false;
    
            this.togglePlayState();
          } else {
            this.togglePauseState();
          }
        };
    
        this.stop = function() {
          window.clearTimeout(timerId);
        };
    
        this.reset = function() {
          if (!paused) {
            window.clearTimeout(timerId);
            start = new Date(), remaining = delay;
            timerId = window.setTimeout(callback, delay);
    
            newone = delayElm.cloneNode(true);
            delayElm.parentNode.replaceChild(newone, delayElm);
            delayElm = newone;
    
            this.togglePlayState();
          }
        }
    
        this.togglePauseState = function() {
          _.lightbox.pause.classList.add('disabled');
          _.lightbox.play.classList.remove('disabled');
          delayElm.style.animationPlayState = 'paused';
          delayElm.style.WebkitAnimationPlayState = 'paused';
        };
        this.togglePlayState = function() {
          _.lightbox.pause.classList.remove('disabled');
          _.lightbox.play.classList.add('disabled');
          delayElm.style.animationPlayState = 'running';
          delayElm.style.WebkitAnimationPlayState = 'running';
        };
    
        this.resume();
      };
    
    
      var timer = new _.autoplay.timer(function() {
        if (_.active) {
          _.events.EventRight();
          timer.reset();
        }
      }, _.autoplay.delay);
      _.autoplay.current = timer;
    
    }
    

---------------------- initAutoplay() ----------------------

This method simply updates the offset of the thumbnails along the bottom of the page, so that the current active image is always in the center of the screen.

updateThumbnailPadding(index) {
  const _ = this;

  var pageWidth = document.body.offsetWidth / 2,
      elementWidth = (_.imagesThumbnails[index].object.parentNode.offsetWidth / 2),
      offsetLeft = _.imagesThumbnails[index].object.parentNode.offsetLeft;

  var offset = (pageWidth - _.imagesThumbnails[index].object.offset().left) - elementWidth;

  (_.lightbox.thumbnailWrapper).style.transform = 'translateX(' + offset + 'px)';
}

---------------------- Others ----------------------

initKeyboard, initTouch don't really do anything special so I won't include them for now, I think there enough to read above...

\$\endgroup\$
  • \$\begingroup\$ Some code snippets don't appear to be working, if anyone can fix that for me that would be a great help! \$\endgroup\$ – Martyn Ball Jul 25 '17 at 10:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.