Source: scrollbar-movie-clip.js

/**
 * @function scrollBarMovieClip
 * @summary
 * Make a movie clip touch/drag scrollable and connect to a scroll bar.
 * @description
 * Required parameters:
 * *  options.draggable Movie Clip to be made scrollable
 * *  options.scrollBarContainer Movie Clip for the scroll bar
 * *  options.scrollBarKnob Movie Clip for the scroll knob which is a child of scrollBarContainer
 *
 * Optional parameters:
 * *  options.mask optional Movie Clip to use as a mask for draggable
 *    this is the display area of the scrollable movie clip, so it should always be passed in
 * *  options.clickableArray optional array of Movie Clips
 * *  options.doVerticalScroll defaults to true
 * *  options.dragUpdate function called after each drag update
 * *  options.dragEnd function called at end of each drag
 *
 * Requires snippets:
 * *  Initialize Gestures
 * *  Constrained Drag
 * *  Scrollable Movie Clip
 * *  Get Movie Clip Bounds (if there is a ClickableArray)
 * *  Point In Bounds (if there is a ClickableArray)
 *
 * @param {Object} options
 * @param {createjs.MovieClip} options.draggable Movie Clip to be made scrollable
 * @param {createjs.MovieClip} options.scrollBarContainer Movie Clip parent of the scroll knob
 * @param {createjs.MovieClip} options.scrollBarKnob Movie Clip of the scroll bar knob (aka thumb)
 * @param {createjs.MovieClip=} options.mask optional Movie Clip to use as a mask for draggble
 * @param {Array=} options.clickableArray optional array of Movie Clips
 * @param {boolean=} options.doVerticalScroll defaults to true
 * @param {number=} options.autoScrollIntervalMs (default 100) Number of milliseconds that autoScroll is repeated
 * @param {number=} options.waitTimeToRestartAutoScrollMs (default 2000) Number of milliseconds to automatically restart
 *        autoscroll after the user interacts (manually moves) the draggable
 * @param {number=} options.startAutoScrollDelayMs (default 0) After calling startAutoScroll, the first movement is not
 *        until after startAutoScrollDelayMs.  The same delay time applies to automatic restarting of autoscroll,
 *        and restarting of autoScroll after the scrollBarContainer is clicked inside.
 * @param {number=} options.autoScrollDx (default 1) Number of pixels to move a horizontal scrollable per autoScroll.
 *        Positive values move the scrollable to the right.
 * @param {number=} options.autoScrollDy (default 1) Number of pixels to move a vertical scrollable per autoScroll
 *        Positive values move the scrollable downward.  Most ads with scrollable text will use negative Dy.
 * @param {boolean=} options.loop defaults to false
 * @param {number=} options.startFraction (default auto calculated) Position draggable will be reset after auto scroll loop
 * @param {number=} options.endFraction (default auto calculated) Position at which draggable loops in auto scroll
 * @param {function=} options.dragUpdate function called after each drag update
 * @param {function=} options.dragEnd function called at end of each drag
 * @returns {{scrollBarReturn: *, scrollableReturn: *, moveToFraction: moveToFraction, moveByDelta: moveByDelta}}
 */
function scrollBarMovieClip (options) {
  var scrollableMovieClip = snippets.scrollableMovieClip
  if (!scrollableMovieClip) {
    console.error('Scrollbar Movie Clip snippet requires Scrollable Movie Clip snippet')
    return
  }
  if (typeof options !== 'object') {
    console.error('Scrollbar Movie Clip must be passed an object as the function parameter')
    return
  }
  if (!(options.scrollBarContainer instanceof window.createjs.MovieClip)) {
    console.error('Scrollbar Movie Clip must be passed options.scrollBarContainer')
    return
  }
  if (!(options.scrollBarKnob instanceof window.createjs.MovieClip)) {
    console.error('Scrollbar Movie Clip must be passed options.scrollBarKnob')
    return
  }
  var dragUpdate = typeof options.dragUpdate === 'function' ? options.dragUpdate : null
  var dragEnd = typeof options.dragEnd === 'function' ? options.dragEnd : null
  var scrollBarKnob = options.scrollBarKnob
  var scrollBarContainer = options.scrollBarContainer
  var loop = options.loop === true
  /*
   * Clone options in case the user passes the same object, to prevent assignment of
   * forceDragRelease = false to the scrollable
   */
  var scrollBarOptions = typeof options.scrollBarOptions === 'object'
    ? Object.assign({}, options.scrollBarOptions) : {}
  var scrollableOptions = typeof options.scrollableOptions === 'object'
    ? Object.assign({}, options.scrollableOptions) : {}
  /*
   * The scrollBarKnob is added to the scrollBarContainer in a Tween, so wait for before testing
   */
  setTimeout(function () {
    if (scrollBarContainer.getChildIndex(scrollBarKnob) < 0) {
      console.error('scrollBarKnob must be a child of scrollBarContainer')
    }
  }, 100)
  var clickableArray = options.clickableArray instanceof Array ? options.clickableArray : []
  /*
   * Remove duplicate clickableArray in case it was added there by accident
   */
  if (clickableArray.length) {
    delete scrollBarOptions.clickableArray
    delete scrollableOptions.clickableArray
  }
  /*
   * Only the scrollable (not the scrollBar) can have loop set
   */
  delete scrollBarOptions.loop
  scrollableOptions.loop = loop
  var doVerticalScroll = !(options.doVerticalScroll === false)

  /*
   * Placeholder function until scrollableReturn is available
   */
  var scrollableMoveToFraction = function () {}
  /*
   * Note: when scrollBar is at fraction 1, scrollable is at fraction 0
   */
  var scrollBarDragUpdate = function (callbackOptions) {
    scrollableMoveToFraction({ fraction: 1 - callbackOptions.fraction })
    dragUpdate && dragUpdate()
  }

  /*
   * Allow clicking on the scrollKnob to propagate the event to the scrollBarContainer to
   * update the scrolling position.
   *
   * Do not allow the scrollBar to be released until a mouseup.
   */
  var scrollBarReturn = scrollableMovieClip(Object.assign(scrollBarOptions, {
    draggable: options.scrollBarKnob,
    doVerticalScroll: doVerticalScroll,
    doLogging: options.doLogging,
    forceDragRelease: false,
    allowDraggableClick: true,
    startFraction: options.startFraction,
    endFraction: options.endFraction,
    dragUpdate: scrollBarDragUpdate
  }))
  var scrollableDragUpdate = function (callbackOptions) {
    scrollBarReturn.moveToFraction({ fraction: 1 - callbackOptions.fraction })
  }
  var moveToFractionCallback = function (options) {
    scrollBarReturn.moveToFraction({ fraction: 1 - options.fraction })
  }
  var moveByDeltaCallback = function () {
    var fraction = scrollableReturn.getFraction()
    scrollBarReturn.moveToFraction({ fraction: 1 - fraction })
  }
  var scrollableReturn = scrollableMovieClip(Object.assign(scrollableOptions, {
    draggable: options.draggable,
    mask: options.mask,
    doVerticalScroll: doVerticalScroll,
    clickableArray: clickableArray,
    allowDraggableClick: options.allowDraggableClick,
    dragUpdate: scrollableDragUpdate,
    dragEnd: dragEnd,
    moveToFractionCallback: moveToFractionCallback,
    moveByDeltaCallback: moveByDeltaCallback,
    autoScrollIntervalMs: options.autoScrollIntervalMs,
    startAutoScrollDelayMs: options.startAutoScrollDelayMs,
    waitTimeToRestartAutoScrollMs: options.waitTimeToRestartAutoScrollMs,
    autoScrollDx: options.autoScrollDx,
    autoScrollDy: options.autoScrollDy,
    doLogging: options.doLogging
  }))
  scrollableMoveToFraction = scrollableReturn.moveToFraction
  var initialFraction = scrollableReturn.getFraction()
  scrollBarReturn.moveToFraction({ fraction: 1 - initialFraction })
  /*
   * Function returned to user to allow arbitrary position change.
   */
  var moveToFraction = function (options) {
    var deltaObj = scrollBarReturn.moveToFraction({ fraction: 1 - options.fraction })
    // var deltaObj = scrollableReturn.moveToFraction(options)
    return deltaObj
  }
  /*
   * Function returned to user to allow arbitrary delta position change.
   * Useful as an event handler on scrollBar arrows.
   */
  var moveByDelta = function (options) {
    var deltaObj = scrollableReturn.moveByDelta(options)
    // var fraction = scrollableReturn.getFraction()
    // scrollBarReturn.moveToFraction({ fraction: 1 - fraction })
    return deltaObj
  }
  /*
   * Update the scrollable position based on the click location in the scrollBarContainer,
   * but do not propagate the event to lower layers.
   */
  scrollBarContainer.on('click', function (evt) {
    scrollableReturn.cancelInertia()
    scrollBarReturn.cancelInertia()
    scrollableReturn.stopAutoScroll()
    var point = scrollBarReturn.getMousePoint({ event: evt, parent: scrollBarContainer })
    var fraction = 1 - scrollBarReturn.getFractionAtPoint(point).fraction
    scrollableReturn.moveToFraction({ fraction: fraction })
    scrollableReturn.restartAutoScroll({ overrideCancel: true })
    evt.preventDefault()
    evt.stopPropagation()
  })
  return {
    scrollBarReturn: scrollBarReturn,
    scrollableReturn: scrollableReturn,
    moveToFraction: moveToFraction,
    moveByDelta: moveByDelta,
    scrollableDragUpdate: scrollableDragUpdate,
    startAutoScroll: scrollableReturn.startAutoScroll,
    stopAutoScroll: scrollableReturn.stopAutoScroll,
    restartAutoScroll: scrollableReturn.restartAutoScroll
  }
}

if (window.module) {
  window.module.scrollBarMovieClip = scrollBarMovieClip
}

window.$b = window.$b || {}
window.$b.snippets = window.$b.snippets || {}
var snippets = window.$b.snippets
snippets.scrollBarMovieClip = scrollBarMovieClip