|
|
/**
|
|
|
* GSAP与Elementor兼容性管理器
|
|
|
* GSAP and Elementor Compatibility Manager
|
|
|
*
|
|
|
* 此文件确保GSAP动画与Elementor内置动画系统的兼容性
|
|
|
* This file ensures compatibility between GSAP animations and Elementor's built-in animation system
|
|
|
*
|
|
|
* @package Nenghui Energy Theme
|
|
|
* @since 1.0.0
|
|
|
*/
|
|
|
|
|
|
(function() {
|
|
|
'use strict';
|
|
|
|
|
|
// GSAP与Elementor兼容性管理器
|
|
|
const GSAPElementorCompatibility = {
|
|
|
initialized: false,
|
|
|
elementorAnimations: new Set(),
|
|
|
gsapAnimations: new Set(),
|
|
|
|
|
|
// 初始化
|
|
|
init: function() {
|
|
|
if (this.initialized) return;
|
|
|
|
|
|
// 添加页面就绪状态标记
|
|
|
document.body.classList.add('gsap-ready');
|
|
|
|
|
|
// 设置GSAP默认值
|
|
|
this.setupGSAPDefaults();
|
|
|
|
|
|
// 设置Elementor钩子
|
|
|
this.setupElementorHooks();
|
|
|
|
|
|
// 设置备用兼容性检查
|
|
|
this.setupFallbackCompatibility();
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
|
|
// 触发初始化完成事件
|
|
|
const event = new CustomEvent('gsapCompatibilityReady');
|
|
|
document.dispatchEvent(event);
|
|
|
},
|
|
|
|
|
|
// 等待依赖加载
|
|
|
waitForDependencies: function(callback) {
|
|
|
let attempts = 0;
|
|
|
const maxAttempts = 100; // 增加等待时间到10秒
|
|
|
|
|
|
const checkDependencies = () => {
|
|
|
attempts++;
|
|
|
const gsapReady = window.gsap && window.ScrollTrigger;
|
|
|
const elementorReady = window.elementorFrontend && window.elementorFrontend.hooks;
|
|
|
|
|
|
if (gsapReady) {
|
|
|
// GSAP准备好了,检查Elementor状态
|
|
|
if (elementorReady || attempts > 30) {
|
|
|
// Elementor也准备好了,或者已经等待足够长时间
|
|
|
callback();
|
|
|
} else {
|
|
|
// 继续等待Elementor
|
|
|
setTimeout(checkDependencies, 100);
|
|
|
}
|
|
|
} else if (attempts < maxAttempts) {
|
|
|
// 继续等待GSAP
|
|
|
setTimeout(checkDependencies, 100);
|
|
|
} else {
|
|
|
// 超时,但仍然尝试继续
|
|
|
callback();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
checkDependencies();
|
|
|
},
|
|
|
|
|
|
// 设置兼容性
|
|
|
setupCompatibility: function() {
|
|
|
// 设置GSAP默认值和基础配置
|
|
|
this.setupGSAPDefaults();
|
|
|
|
|
|
// 检查Elementor是否可用且hooks已准备好
|
|
|
if (window.elementorFrontend && window.elementorFrontend.hooks && typeof window.elementorFrontend.hooks.addAction === 'function') {
|
|
|
this.setupElementorHooks();
|
|
|
} else {
|
|
|
// 延迟再次检查Elementor
|
|
|
setTimeout(() => {
|
|
|
if (window.elementorFrontend && window.elementorFrontend.hooks) {
|
|
|
this.setupElementorHooks();
|
|
|
}
|
|
|
}, 1000);
|
|
|
}
|
|
|
|
|
|
this.setupScrollTriggerCompatibility();
|
|
|
this.setupAnimationConflictResolution();
|
|
|
this.setupGlobalAnimationManager();
|
|
|
},
|
|
|
|
|
|
// 设置Elementor兼容性钩子 - 改进版本
|
|
|
setupElementorHooks: function() {
|
|
|
// 检查Elementor是否可用
|
|
|
if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
|
|
|
this.initElementorHooks();
|
|
|
} else {
|
|
|
// 监听Elementor前端加载事件
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
// 使用更长的延迟确保Elementor完全加载
|
|
|
setTimeout(() => {
|
|
|
if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
|
|
|
this.initElementorHooks();
|
|
|
} else {
|
|
|
this.setupFallbackCompatibility();
|
|
|
}
|
|
|
}, 2000); // 增加延迟到2秒
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 初始化Elementor钩子
|
|
|
initElementorHooks: function() {
|
|
|
try {
|
|
|
// 检查elementorFrontend和hooks是否存在
|
|
|
if (!elementorFrontend || !elementorFrontend.hooks || typeof elementorFrontend.hooks.addAction !== 'function') {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 监听Elementor元素准备就绪事件
|
|
|
elementorFrontend.hooks.addAction('frontend/element_ready/global', (scope) => {
|
|
|
this.handleElementorElementReady(scope);
|
|
|
});
|
|
|
|
|
|
// 监听Elementor动画开始事件
|
|
|
elementorFrontend.hooks.addAction('frontend/element_ready/widget', (scope) => {
|
|
|
this.handleElementorWidgetReady(scope);
|
|
|
});
|
|
|
|
|
|
// 监听页面加载完成
|
|
|
if (elementorFrontend.hooks && typeof elementorFrontend.hooks.addAction === 'function') {
|
|
|
elementorFrontend.hooks.addAction('frontend/init', () => {
|
|
|
this.onElementorFrontendInit();
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
this.setupFallbackCompatibility();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 备用兼容性方案
|
|
|
setupFallbackCompatibility: function() {
|
|
|
// 使用MutationObserver监听DOM变化
|
|
|
const observer = new MutationObserver((mutations) => {
|
|
|
mutations.forEach((mutation) => {
|
|
|
if (mutation.type === 'childList') {
|
|
|
mutation.addedNodes.forEach((node) => {
|
|
|
// 验证节点是否为有效的DOM元素
|
|
|
if (node && node.nodeType === Node.ELEMENT_NODE) {
|
|
|
// 检查是否是Elementor元素
|
|
|
if (node.classList && (
|
|
|
node.classList.contains('elementor-element') ||
|
|
|
node.classList.contains('elementor-widget') ||
|
|
|
node.querySelector('.elementor-element')
|
|
|
)) {
|
|
|
// 添加延迟以确保元素完全加载
|
|
|
setTimeout(() => {
|
|
|
this.handleElementorElement(node);
|
|
|
}, 100);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
observer.observe(document.body, {
|
|
|
childList: true,
|
|
|
subtree: true
|
|
|
});
|
|
|
|
|
|
// 处理已存在的Elementor元素
|
|
|
setTimeout(() => {
|
|
|
const existingElements = document.querySelectorAll('.elementor-element, .elementor-widget');
|
|
|
existingElements.forEach(element => {
|
|
|
// 验证元素有效性
|
|
|
if (element && element.nodeType === Node.ELEMENT_NODE) {
|
|
|
this.handleElementorElement(element);
|
|
|
}
|
|
|
});
|
|
|
}, 1000);
|
|
|
},
|
|
|
|
|
|
// 处理Elementor元素
|
|
|
handleElementorElement: function(element) {
|
|
|
// 延迟处理,确保Elementor动画已初始化
|
|
|
setTimeout(() => {
|
|
|
// 检查元素是否有动画设置
|
|
|
const hasAnimation = element.classList.contains('elementor-invisible') ||
|
|
|
element.hasAttribute('data-settings') ||
|
|
|
element.querySelector('[data-settings*="animation"]');
|
|
|
|
|
|
if (hasAnimation) {
|
|
|
// 暂停该元素的GSAP动画
|
|
|
this.pauseGSAPForElement(element);
|
|
|
|
|
|
// 监听Elementor动画完成
|
|
|
this.waitForElementorAnimation(element, () => {
|
|
|
this.enableGSAPForElement(element);
|
|
|
});
|
|
|
} else {
|
|
|
// 没有Elementor动画,可以安全使用GSAP
|
|
|
this.enableGSAPForElement(element);
|
|
|
}
|
|
|
}, 500);
|
|
|
},
|
|
|
|
|
|
// 暂停元素的GSAP动画
|
|
|
pauseGSAPForElement: function(element) {
|
|
|
if (window.gsap) {
|
|
|
gsap.killTweensOf(element);
|
|
|
gsap.killTweensOf(element.querySelectorAll('*'));
|
|
|
element.setAttribute('data-gsap-paused', 'true');
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 启用元素的GSAP动画
|
|
|
enableGSAPForElement: function(element) {
|
|
|
element.removeAttribute('data-gsap-paused');
|
|
|
element.setAttribute('data-gsap-ready', 'true');
|
|
|
|
|
|
// 触发自定义事件,通知GSAP动画可以开始
|
|
|
const event = new CustomEvent('gsapElementReady', {
|
|
|
detail: { element: element }
|
|
|
});
|
|
|
element.dispatchEvent(event);
|
|
|
},
|
|
|
|
|
|
// 等待Elementor动画完成
|
|
|
waitForElementorAnimation: function(element, callback) {
|
|
|
let attempts = 0;
|
|
|
const maxAttempts = 50; // 5秒超时
|
|
|
|
|
|
const checkAnimation = () => {
|
|
|
// 检查元素是否还有elementor-invisible类
|
|
|
if (!element.classList.contains('elementor-invisible')) {
|
|
|
callback();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
attempts++;
|
|
|
if (attempts < maxAttempts) {
|
|
|
setTimeout(checkAnimation, 100);
|
|
|
} else {
|
|
|
callback(); // 超时也执行回调
|
|
|
}
|
|
|
};
|
|
|
|
|
|
checkAnimation();
|
|
|
},
|
|
|
|
|
|
// 处理Elementor元素
|
|
|
handleElementorElement: function(scope) {
|
|
|
// 验证scope参数
|
|
|
if (!scope) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 确保scope是DOM元素或jQuery对象
|
|
|
let element;
|
|
|
if (scope.jquery) {
|
|
|
// 如果是jQuery对象,获取第一个DOM元素
|
|
|
element = scope[0];
|
|
|
} else if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
|
// 如果是DOM元素
|
|
|
element = scope;
|
|
|
} else {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 验证最终的DOM元素
|
|
|
if (!element || !element.nodeType || element.nodeType !== Node.ELEMENT_NODE) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const $scope = jQuery(element);
|
|
|
|
|
|
// 检查是否有Elementor动画
|
|
|
const hasElementorAnimation = $scope.find('[data-settings*="animation"]').length > 0 ||
|
|
|
$scope.hasClass('elementor-invisible') ||
|
|
|
$scope.find('.elementor-invisible').length > 0;
|
|
|
|
|
|
if (hasElementorAnimation) {
|
|
|
this.elementorAnimations.add(element);
|
|
|
|
|
|
// 为有Elementor动画的元素添加标记
|
|
|
$scope.attr('data-has-elementor-animation', 'true');
|
|
|
|
|
|
// 延迟初始化GSAP动画,避免冲突
|
|
|
setTimeout(() => {
|
|
|
this.initGSAPForElement(element);
|
|
|
}, 300);
|
|
|
} else {
|
|
|
// 没有Elementor动画的元素可以立即初始化GSAP
|
|
|
this.initGSAPForElement(element);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 为元素初始化GSAP动画
|
|
|
initGSAPForElement: function(element) {
|
|
|
// 验证元素是否存在且为有效的DOM元素
|
|
|
if (!element || !element.nodeType || element.nodeType !== Node.ELEMENT_NODE) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查元素是否已经有GSAP动画
|
|
|
if (element.hasAttribute('data-gsap-initialized')) return;
|
|
|
|
|
|
// 标记为已初始化
|
|
|
element.setAttribute('data-gsap-initialized', 'true');
|
|
|
|
|
|
// 这里可以添加特定的GSAP动画逻辑
|
|
|
// 例如:hover效果、滚动触发动画等
|
|
|
},
|
|
|
|
|
|
// 暂停冲突的GSAP动画
|
|
|
pauseConflictingGSAPAnimations: function(scope) {
|
|
|
const gsapElements = scope.querySelectorAll('[data-gsap-initialized]');
|
|
|
|
|
|
gsapElements.forEach(element => {
|
|
|
// 暂停该元素上的所有GSAP动画
|
|
|
gsap.killTweensOf(element);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 设置GSAP默认值
|
|
|
setupGSAPDefaults: function() {
|
|
|
if (!window.gsap) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 设置GSAP默认缓动函数,与Elementor保持一致
|
|
|
gsap.defaults({
|
|
|
ease: "power2.out",
|
|
|
duration: 0.6,
|
|
|
overwrite: "auto" // 自动覆盖冲突的动画
|
|
|
});
|
|
|
|
|
|
// 设置ScrollTrigger默认值
|
|
|
if (window.ScrollTrigger) {
|
|
|
ScrollTrigger.defaults({
|
|
|
toggleActions: "play none none reverse",
|
|
|
markers: false,
|
|
|
scroller: window, // 确保使用正确的滚动容器
|
|
|
refreshPriority: 1 // 提高刷新优先级
|
|
|
});
|
|
|
|
|
|
// 注册ScrollTrigger插件
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
}
|
|
|
|
|
|
// 禁用GSAP的自动CSS前缀,避免与Elementor冲突
|
|
|
gsap.config({
|
|
|
autoSleep: 60,
|
|
|
force3D: false, // 避免强制3D变换
|
|
|
nullTargetWarn: false // 减少警告信息
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 设置ScrollTrigger兼容性
|
|
|
setupScrollTriggerCompatibility: function() {
|
|
|
if (!window.ScrollTrigger) return;
|
|
|
|
|
|
// 监听Elementor的滚动事件
|
|
|
window.addEventListener('scroll', this.debounce(() => {
|
|
|
// 在滚动时刷新ScrollTrigger,确保与Elementor动画同步
|
|
|
ScrollTrigger.refresh();
|
|
|
}, 100), { passive: true });
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
window.addEventListener('resize', this.debounce(() => {
|
|
|
ScrollTrigger.refresh();
|
|
|
}, 250));
|
|
|
|
|
|
// 在Elementor编辑模式下禁用ScrollTrigger
|
|
|
if (this.isElementorEditMode()) {
|
|
|
ScrollTrigger.disable();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 设置动画冲突解决方案
|
|
|
setupAnimationConflictResolution: function() {
|
|
|
// 创建观察器来监听DOM变化
|
|
|
const observer = new MutationObserver((mutations) => {
|
|
|
mutations.forEach((mutation) => {
|
|
|
if (mutation.type === 'childList') {
|
|
|
mutation.addedNodes.forEach((node) => {
|
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
|
this.resolveAnimationConflicts(node);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 开始观察
|
|
|
observer.observe(document.body, {
|
|
|
childList: true,
|
|
|
subtree: true
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 解决动画冲突
|
|
|
resolveAnimationConflicts: function(element) {
|
|
|
// 检查元素是否同时有Elementor和GSAP动画
|
|
|
const hasElementorAnim = element.hasAttribute('data-has-elementor-animation') ||
|
|
|
element.classList.contains('elementor-invisible');
|
|
|
const hasGSAPAnim = element.hasAttribute('data-gsap-initialized');
|
|
|
|
|
|
if (hasElementorAnim && hasGSAPAnim) {
|
|
|
// 优先使用Elementor动画,暂停GSAP动画
|
|
|
gsap.killTweensOf(element);
|
|
|
|
|
|
// 等待Elementor动画完成后再启用GSAP
|
|
|
setTimeout(() => {
|
|
|
this.initGSAPForElement(element);
|
|
|
}, 1000);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 检查是否在Elementor编辑模式
|
|
|
isElementorEditMode: function() {
|
|
|
return window.elementorFrontend && window.elementorFrontend.isEditMode();
|
|
|
},
|
|
|
|
|
|
// 防抖函数
|
|
|
debounce: function(func, wait) {
|
|
|
let timeout;
|
|
|
return function executedFunction(...args) {
|
|
|
const later = () => {
|
|
|
clearTimeout(timeout);
|
|
|
func(...args);
|
|
|
};
|
|
|
clearTimeout(timeout);
|
|
|
timeout = setTimeout(later, wait);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 全局动画管理器
|
|
|
setupGlobalAnimationManager: function() {
|
|
|
// 创建全局动画管理器
|
|
|
window.GSAPAnimationManager = {
|
|
|
activeAnimations: new Map(),
|
|
|
|
|
|
// 注册动画
|
|
|
register: function(id, animation) {
|
|
|
this.activeAnimations.set(id, animation);
|
|
|
},
|
|
|
|
|
|
// 暂停所有动画
|
|
|
pauseAll: function() {
|
|
|
this.activeAnimations.forEach(animation => {
|
|
|
if (animation && animation.pause) {
|
|
|
animation.pause();
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 恢复所有动画
|
|
|
resumeAll: function() {
|
|
|
this.activeAnimations.forEach(animation => {
|
|
|
if (animation && animation.resume) {
|
|
|
animation.resume();
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 清理动画
|
|
|
cleanup: function(id) {
|
|
|
const animation = this.activeAnimations.get(id);
|
|
|
if (animation && animation.kill) {
|
|
|
animation.kill();
|
|
|
}
|
|
|
this.activeAnimations.delete(id);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 监听页面可见性变化
|
|
|
document.addEventListener('visibilitychange', () => {
|
|
|
if (document.hidden) {
|
|
|
window.GSAPAnimationManager.pauseAll();
|
|
|
} else {
|
|
|
window.GSAPAnimationManager.resumeAll();
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 公共API方法
|
|
|
api: {
|
|
|
// 安全地创建GSAP动画
|
|
|
createAnimation: function(target, vars, options = {}) {
|
|
|
if (!window.gsap) {
|
|
|
console.warn('GSAP未加载,无法创建动画');
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
const element = typeof target === 'string' ? document.querySelector(target) : target;
|
|
|
|
|
|
if (!element) {
|
|
|
console.warn('目标元素未找到:', target);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
// 检查是否有Elementor动画冲突
|
|
|
if (element.hasAttribute('data-has-elementor-animation') ||
|
|
|
element.classList.contains('elementor-invisible')) {
|
|
|
console.warn('元素有Elementor动画,延迟GSAP动画');
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
setTimeout(() => {
|
|
|
const animation = gsap.to(element, vars);
|
|
|
if (window.GSAPAnimationManager && options.id) {
|
|
|
window.GSAPAnimationManager.register(options.id, animation);
|
|
|
}
|
|
|
resolve(animation);
|
|
|
}, options.delay || 500);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const animation = gsap.to(element, vars);
|
|
|
if (window.GSAPAnimationManager && options.id) {
|
|
|
window.GSAPAnimationManager.register(options.id, animation);
|
|
|
}
|
|
|
return animation;
|
|
|
},
|
|
|
|
|
|
// 安全地创建ScrollTrigger
|
|
|
createScrollTrigger: function(config) {
|
|
|
if (!window.ScrollTrigger) {
|
|
|
console.warn('ScrollTrigger未加载');
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
if (GSAPElementorCompatibility.isElementorEditMode()) {
|
|
|
console.warn('Elementor编辑模式下跳过ScrollTrigger创建');
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
// 添加默认配置
|
|
|
const defaultConfig = {
|
|
|
refreshPriority: 1,
|
|
|
invalidateOnRefresh: true
|
|
|
};
|
|
|
|
|
|
const finalConfig = Object.assign({}, defaultConfig, config);
|
|
|
|
|
|
try {
|
|
|
return ScrollTrigger.create(finalConfig);
|
|
|
} catch (error) {
|
|
|
console.error('ScrollTrigger创建失败:', error);
|
|
|
return null;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 刷新所有动画
|
|
|
refresh: function() {
|
|
|
try {
|
|
|
if (window.ScrollTrigger) {
|
|
|
ScrollTrigger.refresh();
|
|
|
}
|
|
|
|
|
|
// 触发Elementor的刷新
|
|
|
if (window.elementorFrontend && window.jQuery) {
|
|
|
window.jQuery(window).trigger('resize');
|
|
|
}
|
|
|
|
|
|
console.log('动画刷新完成');
|
|
|
} catch (error) {
|
|
|
console.error('动画刷新失败:', error);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 检查兼容性状态
|
|
|
getCompatibilityStatus: function() {
|
|
|
return {
|
|
|
gsapLoaded: !!window.gsap,
|
|
|
scrollTriggerLoaded: !!window.ScrollTrigger,
|
|
|
elementorLoaded: !!window.elementorFrontend,
|
|
|
compatibilityInitialized: GSAPElementorCompatibility.initialized,
|
|
|
isEditMode: GSAPElementorCompatibility.isElementorEditMode()
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 等待DOM加载完成后初始化
|
|
|
if (document.readyState === 'loading') {
|
|
|
document.addEventListener('DOMContentLoaded', () => GSAPElementorCompatibility.init());
|
|
|
} else {
|
|
|
GSAPElementorCompatibility.init();
|
|
|
}
|
|
|
|
|
|
// 暴露API到全局作用域
|
|
|
window.GSAPElementorCompatibility = GSAPElementorCompatibility;
|
|
|
window.GSAPElementorAPI = GSAPElementorCompatibility.api;
|
|
|
|
|
|
})();// Updated at Wed Oct 15 04:15:42 AM CST 2025
|