my-animation-lib
Version:
A powerful animation library combining Three.js, GSAP, custom scroll triggers, and advanced effects with MathUtils integration
453 lines (404 loc) • 12.7 kB
JavaScript
/**
* DOM Utilities for common DOM operations
*/
export class DOMUtils {
/**
* Create an element with attributes and content
* @param {string} tag - HTML tag name
* @param {Object} attributes - Element attributes
* @param {string|Element} content - Element content
* @returns {HTMLElement}
*/
static createElement(tag, attributes = {}, content = '') {
const element = document.createElement(tag);
// Set attributes
Object.keys(attributes).forEach(key => {
if (key === 'className') {
element.className = attributes[key];
} else if (key === 'style' && typeof attributes[key] === 'object') {
Object.assign(element.style, attributes[key]);
} else {
element.setAttribute(key, attributes[key]);
}
});
// Set content
if (typeof content === 'string') {
element.textContent = content;
} else if (content instanceof Element) {
element.appendChild(content);
}
return element;
}
/**
* Get element by selector
* @param {string} selector - CSS selector
* @param {Element} parent - Parent element (defaults to document)
* @returns {Element|null}
*/
static getElement(selector, parent = document) {
return parent.querySelector(selector);
}
/**
* Get all elements by selector
* @param {string} selector - CSS selector
* @param {Element} parent - Parent element (defaults to document)
* @returns {NodeList}
*/
static getElements(selector, parent = document) {
return parent.querySelectorAll(selector);
}
/**
* Add event listener with options
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @param {Object} options - Event options
*/
static addEvent(element, event, handler, options = {}) {
element.addEventListener(event, handler, options);
}
/**
* Remove event listener
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @param {Object} options - Event options
*/
static removeEvent(element, event, handler, options = {}) {
element.removeEventListener(event, handler, options);
}
/**
* Add multiple event listeners
* @param {Element} element - Target element
* @param {Object} events - Event handlers object
* @param {Object} options - Event options
*/
static addEvents(element, events, options = {}) {
Object.keys(events).forEach(event => {
this.addEvent(element, event, events[event], options);
});
}
/**
* Remove multiple event listeners
* @param {Element} element - Target element
* @param {Object} events - Event handlers object
* @param {Object} options - Event options
*/
static removeEvents(element, events, options = {}) {
Object.keys(events).forEach(event => {
this.removeEvent(element, event, events[event], options);
});
}
/**
* Add CSS class to element
* @param {Element} element - Target element
* @param {string} className - CSS class name
*/
static addClass(element, className) {
element.classList.add(className);
}
/**
* Remove CSS class from element
* @param {Element} element - Target element
* @param {string} className - CSS class name
*/
static removeClass(element, className) {
element.classList.remove(className);
}
/**
* Toggle CSS class on element
* @param {Element} element - Target element
* @param {string} className - CSS class name
* @returns {boolean} - Whether class was added
*/
static toggleClass(element, className) {
return element.classList.toggle(className);
}
/**
* Check if element has CSS class
* @param {Element} element - Target element
* @param {string} className - CSS class name
* @returns {boolean}
*/
static hasClass(element, className) {
return element.classList.contains(className);
}
/**
* Set element style
* @param {Element} element - Target element
* @param {string|Object} property - CSS property or style object
* @param {string} value - CSS value (if property is string)
*/
static setStyle(element, property, value) {
if (typeof property === 'object') {
Object.assign(element.style, property);
} else {
element.style[property] = value;
}
}
/**
* Get element style
* @param {Element} element - Target element
* @param {string} property - CSS property
* @returns {string}
*/
static getStyle(element, property) {
return window.getComputedStyle(element)[property];
}
/**
* Set element attributes
* @param {Element} element - Target element
* @param {Object} attributes - Attributes object
*/
static setAttributes(element, attributes) {
Object.keys(attributes).forEach(key => {
element.setAttribute(key, attributes[key]);
});
}
/**
* Get element attribute
* @param {Element} element - Target element
* @param {string} attribute - Attribute name
* @returns {string|null}
*/
static getAttribute(element, attribute) {
return element.getAttribute(attribute);
}
/**
* Remove element attribute
* @param {Element} element - Target element
* @param {string} attribute - Attribute name
*/
static removeAttribute(element, attribute) {
element.removeAttribute(attribute);
}
/**
* Append child to element
* @param {Element} parent - Parent element
* @param {Element} child - Child element
*/
static appendChild(parent, child) {
parent.appendChild(child);
}
/**
* Insert element before reference
* @param {Element} parent - Parent element
* @param {Element} element - Element to insert
* @param {Element} reference - Reference element
*/
static insertBefore(parent, element, reference) {
parent.insertBefore(element, reference);
}
/**
* Remove element from DOM
* @param {Element} element - Element to remove
*/
static removeElement(element) {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
/**
* Clear element content
* @param {Element} element - Target element
*/
static clearContent(element) {
element.innerHTML = '';
}
/**
* Get element dimensions and position
* @param {Element} element - Target element
* @returns {Object} - Object with width, height, top, left, right, bottom
*/
static getElementRect(element) {
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
right: rect.right + window.scrollX,
bottom: rect.bottom + window.scrollY
};
}
/**
* Check if element is in viewport
* @param {Element} element - Target element
* @param {number} threshold - Visibility threshold (0-1)
* @returns {boolean}
*/
static isInViewport(element, threshold = 0) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0);
const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0);
const visibleArea = visibleHeight * visibleWidth;
const totalArea = rect.height * rect.width;
return visibleArea / totalArea > threshold;
}
/**
* Get scroll position
* @returns {Object} - Object with x and y scroll positions
*/
static getScrollPosition() {
return {
x: window.pageXOffset || document.documentElement.scrollLeft,
y: window.pageYOffset || document.documentElement.scrollTop
};
}
/**
* Scroll to element
* @param {Element} element - Target element
* @param {Object} options - Scroll options
*/
static scrollToElement(element, options = {}) {
const {
behavior = 'smooth',
block = 'start',
inline = 'nearest'
} = options;
element.scrollIntoView({ behavior, block, inline });
}
/**
* Scroll to position
* @param {number} x - X position
* @param {number} y - Y position
* @param {Object} options - Scroll options
*/
static scrollToPosition(x, y, options = {}) {
const {
behavior = 'smooth'
} = options;
window.scrollTo({
left: x,
top: y,
behavior
});
}
/**
* Debounce function execution
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @returns {Function} - Debounced function
*/
static debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Throttle function execution
* @param {Function} func - Function to throttle
* @param {number} limit - Time limit in milliseconds
* @returns {Function} - Throttled function
*/
static throttle(func, limit) {
let inThrottle;
return function executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* Wait for element to exist in DOM
* @param {string} selector - CSS selector
* @param {number} timeout - Timeout in milliseconds
* @returns {Promise<Element>}
*/
static waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
const observer = new MutationObserver((mutations) => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
}, timeout);
});
}
/**
* Create and dispatch custom event
* @param {string} eventName - Event name
* @param {Object} detail - Event detail
* @param {Element} target - Target element (defaults to document)
*/
static dispatchCustomEvent(eventName, detail = {}, target = document) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
target.dispatchEvent(event);
}
/**
* Get computed styles for element
* @param {Element} element - Target element
* @param {Array<string>} properties - CSS properties to get
* @returns {Object} - Object with property values
*/
static getComputedStyles(element, properties = []) {
const styles = window.getComputedStyle(element);
const result = {};
properties.forEach(property => {
result[property] = styles[property];
});
return result;
}
/**
* Animate element property
* @param {Element} element - Target element
* @param {string} property - CSS property
* @param {*} from - Start value
* @param {*} to - End value
* @param {Object} options - Animation options
*/
static animateProperty(element, property, from, to, options = {}) {
const {
duration = 1000,
easing = 'ease',
onUpdate = null,
onComplete = null
} = options;
const startTime = performance.now();
const startValue = from;
const changeValue = to - from;
function animate(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const currentValue = startValue + changeValue * progress;
element.style[property] = currentValue;
if (onUpdate) {
onUpdate(currentValue, progress);
}
if (progress < 1) {
requestAnimationFrame(animate);
} else if (onComplete) {
onComplete();
}
}
requestAnimationFrame(animate);
}
}