zingchart / zingtouch

A JavaScript touch gesture detection library for the modern web
https://zingchart.github.io/zingtouch/
MIT License
2.12k stars 135 forks source link

Add callbacks for start, move and end. #61

Open whoacowboy opened 5 years ago

whoacowboy commented 5 years ago

Great library, thank you.

I don't know if I am doing this the right way, but I have been tying into the start and end function of the gestures and it is a bit of a PIA.

it looks like this.

  let pan = new ZingTouch.Pan({numInputs: 1})
  let that = this
  pan.start  = function (inputs) {
    inputs.forEach((input) => {
      const progress = input.getGestureProgress(this.getId())
      progress.active = true
      progress.lastEmitted = {
        x: input.current.x,
        y: input.current.y,
      }
    })
    that.doThatThing(inputs)
  }
  pan.end = function (inputs) {
    inputs.forEach((input) => {
      const progress = input.getGestureProgress(this.getId())
      progress.active = false
    })
    that.doThatOtherThing(inputs)
    return null
  }

I was thinking it would be great to just pass some callbacks. You could do it something like this. I can make a PR if your interested.

/**
 * @file Pan.js
 * Contains the Pan class
 */

import Gesture from './Gesture.js';
import util from './../core/util.js';

const DEFAULT_INPUTS = 1;
const DEFAULT_MIN_THRESHOLD = 1;

/**
 * A Pan is defined as a normal movement in any direction on a screen.
 * Pan gestures do not track start events and can interact with distance gestures
 * @class Pan
 */
class Pan extends Gesture {
  /**
   * Constructor function for the Pan class.
   * @param {Object} [options] - The options object.
   * @param {Number} [options.numInputs=1] - Number of inputs for the
   *  Pan gesture.
   * @param {Number} [options.threshold=1] - The minimum number of
   * @param {Function} [options.onStart] - The on start callback
   * @param {Function} [options.onMove] - The on move callback
   * @param {Function} [options.onEnd] - The on end callback
   */
  constructor(options) {
    super();

    /**
     * The type of the Gesture.
     * @type {String}
     */
    this.type = 'pan';

    /**
     * The number of inputs to trigger a Pan can be variable,
     * and the maximum number being a factor of the browser.
     * @type {Number}
     */
    this.numInputs = (options && options.numInputs) ?
      options.numInputs : DEFAULT_INPUTS;

    /**
     * The minimum amount in pixels the pan must move until it is fired.
     * @type {Number}
     */
    this.threshold = (options && options.threshold) ?
      options.threshold : DEFAULT_MIN_THRESHOLD;

    /**
     * The on start callback
     */
    if (options && options.onStart && typeof options.onStart === "function") {
        this.onStart = options.onStart
    }      
    /**
     * The on move callback
     */
    if (options && options.onMove && typeof options.onMove === "function") {
        this.onMove = options.onMove
    }      
    /**
     * The on end callback
     */
    if (options && options.onEnd && typeof options.onEnd === "function") {
        this.onEnd = options.onEnd
    }
  }

  /**
   * Event hook for the start of a gesture. Marks each input as active,
   * so it can invalidate any end events.
   * @param {Array} inputs
   */
  start(inputs) {
    inputs.forEach((input) => {
      const progress = input.getGestureProgress(this.getId());
      progress.active = true;
      progress.lastEmitted = {
        x: input.current.x,
        y: input.current.y,
      };
    });
    if(this.onStart) {
        this.onStart(inputs);
    }
  }

  /**
   * move() - Event hook for the move of a gesture.
   * Fired whenever the input length is met, and keeps a boolean flag that
   * the gesture has fired at least once.
   * @param {Array} inputs - The array of Inputs on the screen
   * @param {Object} state - The state object of the current region.
   * @param {Element} element - The element associated to the binding.
   * @return {Object} - Returns the distance in pixels between the two inputs.
   */
  move(inputs, state, element) {
    if (this.numInputs !== inputs.length) return null;

    const output = {
      data: [],
    };

    inputs.forEach( (input, index) => {
      const progress = input.getGestureProgress(this.getId());
      const distanceFromLastEmit = util.distanceBetweenTwoPoints(
        progress.lastEmitted.x,
        progress.lastEmitted.y,
        input.current.x,
        input.current.y
      );
      const reachedThreshold = distanceFromLastEmit >= this.threshold;

      if (progress.active && reachedThreshold) {
        output.data[index] = packData( input, progress );
        progress.lastEmitted.x = input.current.x;
        progress.lastEmitted.y = input.current.y;
      }
    });

    if(this.onMove) {
        this.onMove(inputs, state, element);
    }
    return output;

    function packData( input, progress ) {
      const distanceFromOrigin = util.distanceBetweenTwoPoints(
        input.initial.x,
        input.current.x,
        input.initial.y,
        input.current.y
      );
      const directionFromOrigin = util.getAngle(
        input.initial.x,
        input.initial.y,
        input.current.x,
        input.current.y
      );
      const currentDirection = util.getAngle(
        progress.lastEmitted.x,
        progress.lastEmitted.y,
        input.current.x,
        input.current.y
      );
      const change = {
        x: input.current.x - progress.lastEmitted.x,
        y: input.current.y - progress.lastEmitted.y,
      };

      return {
        distanceFromOrigin,
        directionFromOrigin,
        currentDirection,
        change,
      };
    }
  }

  /* move*/

  /**
   * end() - Event hook for the end of a gesture. If the gesture has at least
   * fired once, then it ends on the first end event such that any remaining
   * inputs will not trigger the event until all inputs have reached the
   * touchend event. Any touchend->touchstart events that occur before all
   * inputs are fully off the screen should not fire.
   * @param {Array} inputs - The array of Inputs on the screen
   * @return {null} - null if the gesture is not to be emitted,
   *  Object with information otherwise.
   */
  end(inputs) {
    inputs.forEach((input) => {
      const progress = input.getGestureProgress(this.getId());
      progress.active = false;
    });
    return null;
  }

    if(this.onEnd) {
      this.onEnd(onEnd);
    }
  /* end*/
}

export default Pan;
tryhardest commented 4 years ago

Did you ever do a PR @whoacowboy ? How was your experience with Zing? Did you previously use Interact.js, hammer, quo or anything?

whoacowboy commented 4 years ago

@tryhardest I forked the rep, added the events to the Pan (and some degree stuff that is suspect) which is what I needed, and now I use my forked repo. The library is great, I really like that it tracks multiple touch events. I think the Zing folks have moved on. They said that they opted for another solution and they don't use ZingTouch anymore. When I asked what they opted for they went dark. Pretty much most of my requests have met the same response. I used hammer.js too, but opted to switch to this one because hammer didn't seem like it was being maintained, ha.

here is the forked repo if you are interested.

figurosity/zingtouch

tryhardest commented 4 years ago

Thanks @whoacowboy yes it does looked unmaintained. We used to use Hammer and Quo back in the day and hammer might not be maintained but still has wide use from the looks of it. But so much has changed past CPL yrs. Ever seen or tinkered with Interact.js our touchy.js? Check them too. I'll check out your fork thanks.

whoacowboy commented 4 years ago

@tryhardest I haven't tried either of those interact.js looks like it actively maintained, bugs are being closed 4 days ago. Touchy looks like it isn't maintained at all. Thanks for the tip, I'll check out interact when i get the chance.