Home Reference Source

src/utils.js

/**
 * measure distance between two points
 * @param {number} x1
 * @param {number} y1
 * @param {number} x2
 * @param {number} y2
 */
export function distance(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
}

/**
 * find shortest distance from point to HTMLElement's bounding box
 * from: https://gamedev.stackexchange.com/questions/44483/how-do-i-calculate-distance-between-a-point-and-an-axis-aligned-rectangle
 * @param {number} x
 * @param {number} y
 * @param {HTMLElement} element
 */
export function distancePointElement(px, py, element) {
    const pos = toGlobal(element)
    const width = element.offsetWidth
    const height = element.offsetHeight
    const x = pos.x + width / 2
    const y = pos.y + height / 2
    const dx = Math.max(Math.abs(px - x) - width / 2, 0)
    const dy = Math.max(Math.abs(py - y) - height / 2, 0)
    return dx * dx + dy * dy
}

/**
 * determine whether the mouse is inside an element
 * @param {HTMLElement} dragging
 * @param {HTMLElement} element
 */
export function inside(x, y, element) {
    const pos = toGlobal(element)
    const x1 = pos.x
    const y1 = pos.y
    const w1 = element.offsetWidth
    const h1 = element.offsetHeight
    return x >= x1 && x <= x1 + w1 && y >= y1 && y <= y1 + h1
}

/**
 * determines global location of a div
 * from https://stackoverflow.com/a/26230989/1955997
 * @param {HTMLElement} e
 * @returns {PointLike}
 */
export function toGlobal(e) {
    const box = e.getBoundingClientRect()

    const body = document.body
    const docEl = document.documentElement

    const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop
    const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft

    const clientTop = docEl.clientTop || body.clientTop || 0
    const clientLeft = docEl.clientLeft || body.clientLeft || 0

    const top = box.top + scrollTop - clientTop
    const left = box.left + scrollLeft - clientLeft

    return { y: Math.round(top), x: Math.round(left) }
}

/**
 * @typedef {object} PointLike
 * @property {number} x
 * @property {number} y
 */

/**
 * combines options and default options
 * @param {object} options
 * @param {object} defaults
 * @returns {object} options+defaults
 */
export function options(options, defaults) {
    options = options || {}
    for (let option in defaults) {
        options[option] = typeof options[option] !== 'undefined' ? options[option] : defaults[option]
    }
    return options
}

/**
 * set a style on an element
 * @param {HTMLElement} element
 * @param {string} style
 * @param {(string|string[])} value - single value or list of possible values (test each one in order to see if it works)
 */
export function style(element, style, value) {
    if (Array.isArray(value)) {
        for (let entry of value) {
            element.style[style] = entry
            if (element.style[style] === entry) {
                break
            }
        }
    } else {
        element.style[style] = value
    }
}

/**
 * calculate percentage of overlap between two boxes
 * from https://stackoverflow.com/a/21220004/1955997
 * @param {number} xa1
 * @param {number} ya1
 * @param {number} xa2
 * @param {number} xa2
 * @param {number} xb1
 * @param {number} yb1
 * @param {number} xb2
 * @param {number} yb2
 */
export function percentage(xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2) {
    const sa = (xa2 - xa1) * (ya2 - ya1)
    const sb = (xb2 - xb1) * (yb2 - yb1)
    const si = Math.max(0, Math.min(xa2, xb2) - Math.max(xa1, xb1)) * Math.max(0, Math.min(ya2, yb2) - Math.max(ya1, yb1))
    const union = sa + sb - si
    if (union !== 0) {
        return si / union
    } else {
        return 0
    }
}

export function removeChildren(element) {
    while (element.firstChild) {
        element.firstChild.remove()
    }
}

export function html(options) {
    options = options || {}
    const object = document.createElement(options.type || 'div')
    if (options.parent) {
        options.parent.appendChild(object)
    }
    if (options.defaultStyles) {
        styles(object, options.defaultStyles)
    }
    if (options.styles) {
        styles(object, options.styles)
    }
    if (options.html) {
        object.innerHTML = options.html
    }
    if (options.id) {
        object.id = options.id
    }
    return object
}

export function styles(object, styles) {
    for (let style in styles) {
        if (Array.isArray(styles[style])) {
            for (let entry of styles[style]) {
                object.style[style] = entry
                if (object.style[style] === entry) {
                    break
                }
            }
        } else {
            object.style[style] = styles[style]
        }
    }
}

export function getChildIndex(parent, child) {
    let index = 0
    for (let entry of parent.children) {
        if (entry === child) {
            return index
        }
        index++
    }
    return -1
}