Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom JS events #122

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions assets/scripts/utils/debounce.js

This file was deleted.

180 changes: 180 additions & 0 deletions assets/scripts/utils/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { debounce } from './tickers'

/**
* @typedef {object} RegisteredCustomEvent
*
* @property {EventTarget} target - The event target ({@see Window} or {@see Node}).
* @property {string} type - The custom event name.
*/
/** @type {RegisteredCustomEvent[]} */

REGISTERED_CUSTOM_EVENTS = []

/**
* Determines if the given object is the {@see Window}.
*
* @param {object} obj
* @return {boolean}
*/

const isWindow = obj => obj === window


/**
* Determines if the given object implements {@see EventTarget}.
*
* @param {object} obj
* @return {boolean}
*/

const isEventTarget = obj => (obj instanceof EventTarget)


/**
* Determines if the target already has the event attached.
*
* @param {EventTarget} target
* @param {string} type - The custom event name.
* @return {boolean}
*/

const isCustomEventRegistered = (target, type) => REGISTERED_CUSTOM_EVENTS.some(e => e.target === target && e.type === type)


/**
* Registers the custom event with the given target, if not already registered.
*
* @param {EventTarget} target
* @param {string} type - The custom event name.
* @return {void}
*/

const addCustomEvent = (target, type) => {
if (!isCustomEventDefined(target, type)) {
CUSTOM_EVENT_LISTENERS.push({
target,
type
})
}
}


/**
* Adds a custom "start" event for the given target.
*
* Internally, this function adds a debounced event listener on
* the given `event` to trigger the custom `<event>start` event.
*
* @param {EventTarget} target
* @param {string} type - The base event name.
* @param {number} [delay] - The number of milliseconds to wait
* before dispatching the custom event.
* @throws Error If the target is invalid.
* @throws Error If the custom event is already defined.
* @return {void}
*/

const addStartEvent = (target, type, delay = 200) => {

const customType = `${type}start`

if (!isEventTarget(target)) {
throw new Error(`addStartEvent: target parameter must be an instance of EventTarget`)
}

if (isCustomEventDefined(target, customType)) {
throw new Error(`addStartEvent: '${customType}' already exists for target parameter`)
}

addCustomEvent(target, customType)
const startEvent = new CustomEvent(customType)

target.addEventListener(event, debounce(() => {
target.dispatchEvent(startEvent)
}, delay, true))
}


/**
* Adds a custom "end" event for the given target.
*
* Internally, this function adds a debounced event listener on
* the given `event` to trigger the custom `<event>end` event.
*
* @param {EventTarget} target
* @param {string} type - The base event name.
* @param {number} [delay] - The number of milliseconds to wait
* before dispatching the custom event.
* @throws Error If the target is invalid.
* @throws Error If the custom event is already defined.
* @return {void}
*/

const addEndEvent = (target, type, delay = 200) => {

const customType = `${event}end`

if (!isEventTarget(target)) {
throw new Error(`addEndEvent: target parameter must be an instance of EventTarget`)
}

if (isCustomEventDefined(target, customType)) {
throw new Error(`addEndEvent: '${customType}' already exists for target parameter`)
}

addCustomEvent(target, customType)
const endEvent = new CustomEvent(customType)

target.addEventListener(event, debounce(() => {
target.dispatchEvent(endEvent)
}, delay))
}


/**
* Adds custom scroll "up" and "down" events for the given target.
*
* Internally, this function adds an event listener on
* the scroll event to detect the direction and trigger
* the custom `scrollup` and `scrolldown` events.
*
* @param {EventTarget} [target] - If omitted, the custom event
* if attached to the Window.
* @throws Error If the target is invalid.
* @return {void}
*/

const addScrollDirectionEvents = (target = window) => {

if (!isEventTarget(target)) {
throw new Error(`addScrollDirectionEvents: target parameter must be an instance of EventTarget`)
}

let scrollTop = target.scrollTop
let previousScrollTop = scrollTop
let direction = 0
const scrollUp = new CustomEvent('scrollup')
const scrollDown = new CustomEvent('scrolldown')
const scrollProperty = isWindow(target) ? 'scrollY' : 'scrollTop'

target.addEventListener('scroll', () => {
scrollTop = target[scrollProperty]
// Scroll up
if (scrollTop < previousScrollTop && direction > -1) {
target.dispatchEvent(scrollUp)
direction = -1
// Scroll down
} else if (scrollTop > previousScrollTop && direction < 1) {
target.dispatchEvent(scrollDown)
direction = 1
}
previousScrollTop = scrollTop
})
}


export {
addStartEvent,
addEndEvent,
addScrollDirectionEvents,
}
78 changes: 78 additions & 0 deletions assets/scripts/utils/tickers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Creates a debounced function.
*
* A debounced function delays invoking `callback` until after
* `delay` milliseconds have elapsed since the last time the
* debounced function was invoked.
*
* Useful for behaviour that should only happen _before_ or
* _after_ an event has stopped occurring.
*
* @template {function} T
*
* @param {T} callback - The function to debounce.
* @param {number} delay - The number of milliseconds to wait.
* @param {boolean} [immediate] -
* If `true`, `callback` is invoked before `delay`.
* If `false`, `callback` is invoked after `delay`.
* @return {function<T>} The new debounced function.
*/

const debounce = (callback, delay, immediate = false) => {
let timeout = null

return (...args) => {
clearTimeout(timeout)

const later = () => {
timeout = null
if (!immediate) {
callback(...args)
}
}

if (immediate && !timeout) {
callback(...args)
}

timeout = setTimeout(later, delay)
}
}


/**
* Creates a throttled function.
*
* A throttled function invokes `callback` at most once per every
* `delay` milliseconds.
*
* Useful for rate-limiting an event that occurs in quick succession.
*
* @template {function} T
*
* @param {T} callback - The function to throttle.
* @param {number} delay - The number of milliseconds to wait.
* @return {function<T>} The new throttled function.
*/

const throttle = (callback, delay) => {
let timeout = false

return (...args) => {
if (!timeout) {
timeout = true

callback(...args)

setTimeout(() => {
timeout = false
}, delay)
}
}
}


export {
debounce,
throttle
}
Loading