Re: 正在做etani,ejtile的動畫套件
发表于 : 2025年 10月 25日 10:44
grok expert
代码: 全选
### 新增函式位置
在程式碼的 `// Initialize the animation control panel on window load` 註釋之前(即 window.addEventListener('load', ... ) 之前),新增以下全局獨立函式:
```javascript
// Function to append animation tag based on id, type, and attr
function etaniAppendAnimate(id, type, attr) {
if (!etani_clone) return;
const isRepeat = document.querySelector('.etaniModeRepeat.active');
// Determine UI animateType based on type and attr
let animateTypeUI = attr;
if (type === 'animateTransform' && attr === 'transform') {
animateTypeUI = 'transform';
} else if (type === 'animate' && attr === 'opacity') {
animateTypeUI = 'opacity';
}
const defs = etani_clone.querySelector('defs');
const targetG = defs ? defs.querySelector(`g#${id}`) : null;
let targetElement;
if (targetG) {
targetElement = etani_clone.querySelector(`.etdrop use[href="#${id}"]`);
if (!targetElement) return;
} else {
targetElement = etani_clone;
}
const ns = 'http://www.w3.org/2000/svg';
if (animateTypeUI === 'transform') {
// Get original transform values
const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${id}"]`);
const originalTransformString = originalUseElement ? originalUseElement.getAttribute('transform') || '' : '';
const originalTransforms = parseTransform(originalTransformString);
// Base function to create animateTransform
const baseAnimate = (subType, initialValue) => {
const anim = document.createElementNS(ns, 'animateTransform');
anim.setAttribute('attributeName', 'transform');
anim.setAttribute('attributeType', 'XML');
anim.setAttribute('type', subType);
anim.setAttribute('values', initialValue);
anim.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
if (isRepeat) anim.setAttribute('repeatCount', 'indefinite');
if (subType === 'scale' || subType === 'rotate') {
anim.setAttribute('additive', 'sum');
}
return anim;
};
// Create and append animations
const translateAnim = baseAnimate('translate', originalTransforms.translate);
const scaleAnim = baseAnimate('scale', originalTransforms.scale);
const rotateAnim = baseAnimate('rotate', originalTransforms.rotate);
if (!targetG) {
translateAnim.setAttribute('href', `#${id}`);
scaleAnim.setAttribute('href', `#${id}`);
rotateAnim.setAttribute('href', `#${id}`);
}
targetElement.appendChild(translateAnim);
targetElement.appendChild(scaleAnim);
targetElement.appendChild(rotateAnim);
} else {
// Create general animation tag
const animate = document.createElementNS(ns, type);
if (attr) animate.setAttribute('attributeName', attr);
animate.setAttribute('values', '1');
animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
if (!targetG) {
animate.setAttribute('href', `#${id}`);
}
targetElement.appendChild(animate);
}
// Update UI for the specific id
const itemRight = document.querySelector(`.etaniItem[data-use-id="${id}"] .etaniItemRight`);
if (!itemRight || itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"]`)) return;
// --- Start of HTML control creation (copied and adapted from handleAllAppendAnimationClick) ---
const etaniAnimate = document.createElement('div');
etaniAnimate.className = 'etaniAnimate';
etaniAnimate.setAttribute('data-type', animateTypeUI);
const nameSpan = document.createElement('span');
nameSpan.className = 'etaniAnimateName';
nameSpan.textContent = animateTypeUI;
const durSpan = document.createElement('span');
durSpan.className = 'etaniAnimateDur';
durSpan.textContent = 'dur: 0s';
durSpan.addEventListener('click', () => {
const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
const newDur = prompt('Enter duration in seconds:', currentDur);
if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
let animates;
if (animateTypeUI === 'transform') {
animates = etani_clone.querySelectorAll(`.etdrop use[href="#${id}"] animateTransform`);
} else {
animates = [etani_clone.querySelector(`.etdrop use[href="#${id}"] animate[attributeName="${attr}"]`)];
}
const currentValues = animates[0]?.getAttribute('values')?.split(';') || [];
const currentValuesCount = currentValues.length;
const isIntegerDur = Number.isInteger(currentDur);
const isIntegerNewDur = Number.isInteger(newDur);
if (isIntegerDur && isIntegerNewDur && newDur > currentDur) {
const valuesToAdd = newDur - currentDur;
const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${id}"]`);
if (originalUseElement) {
let originalTransforms;
let valueToAdd;
if (animateTypeUI === 'transform') {
const originalTransformString = originalUseElement.getAttribute('transform') || '';
originalTransforms = parseTransform(originalTransformString);
} else {
valueToAdd = '1';
}
animates.forEach(animate => {
const currentValueString = animate.getAttribute('values') || '';
if (animateTypeUI === 'transform') {
const subType = animate.getAttribute('type').toLowerCase();
valueToAdd = subType === 'translate' ? originalTransforms.translate :
subType === 'scale' ? originalTransforms.scale :
subType === 'rotate' ? originalTransforms.rotate : '';
}
let newValues = currentValueString ? currentValueString.split(';') : [];
for (let i = 0; i < valuesToAdd; i++) {
newValues.push(valueToAdd);
}
animate.setAttribute('values', newValues.join(';'));
});
// Update UI to add new etaniAVItems
const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"] .etaniAV`);
if (etaniAV) {
const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
let existingLetters = Array.from(existingItems).map(item => item.textContent);
for (let i = 0; i < valuesToAdd; i++) {
const newAVItem = document.createElement('span');
newAVItem.className = 'etaniAVItem';
newAVItem.textContent = animateTypeUI === 'transform' ? findFirstMissingLetter(existingLetters) : '1';
if (animateTypeUI === 'transform') existingLetters.push(newAVItem.textContent);
newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));
etaniAV.appendChild(newAVItem);
}
}
}
}
animates.forEach(animate => {
if (newDur > 0) {
animate.setAttribute('dur', `${newDur}s`);
if (isRepeat) {
animate.removeAttribute('fill');
animate.setAttribute('repeatCount', 'indefinite');
} else {
animate.removeAttribute('repeatCount');
animate.setAttribute('fill', 'freeze');
}
} else {
animate.removeAttribute('dur');
animate.removeAttribute('fill');
animate.removeAttribute('repeatCount');
}
});
durSpan.textContent = `dur: ${newDur}s`;
updateEtaniResult();
}
});
const etaniAnimateAttr = document.createElement('div');
etaniAnimateAttr.className = 'etaniAnimateAttr';
etaniAnimateAttr.appendChild(nameSpan);
etaniAnimateAttr.appendChild(durSpan);
etaniAnimate.appendChild(etaniAnimateAttr);
const frSpan = document.createElement('span');
frSpan.className = 'etaniAnimateFR';
frSpan.textContent = document.querySelector('.etaniModeMixed.active') ? 'freeze' : 'repeat';
frSpan.style.display = document.querySelector('.etaniModeMixed.active') ? 'inline-block' : 'none';
frSpan.addEventListener('click', () => {
const currentValue = frSpan.textContent;
const newValue = currentValue === 'freeze' ? 'repeat' : 'freeze';
frSpan.textContent = newValue;
let animateElem;
if (animateTypeUI === 'transform') {
animateElem = etani_clone.querySelectorAll(`.etdrop use[href="#${id}"] animateTransform`);
} else {
animateElem = [etani_clone.querySelector(`.etdrop use[href="#${id}"] animate[attributeName="${attr}"]`)];
}
animateElem.forEach(animate => {
if (animate.hasAttribute('fill') || animate.hasAttribute('repeatCount')) {
if (newValue === 'repeat') {
animate.removeAttribute('fill');
animate.setAttribute('repeatCount', 'indefinite');
} else {
animate.removeAttribute('repeatCount');
animate.setAttribute('fill', 'freeze');
}
}
});
updateEtaniResult();
});
etaniAnimateAttr.appendChild(frSpan);
// Add the attribute add button
const attrAddSpan = document.createElement('span');
attrAddSpan.className = 'etaniAnimateAttrAdd';
attrAddSpan.textContent = '+';
attrAddSpan.addEventListener('click', (e) => showDropdown(e, animateTypeUI, id));
etaniAnimateAttr.appendChild(attrAddSpan);
// Handle clicks on existing attribute spans
etaniAnimateAttr.addEventListener('click', (e) => {
if (e.target.classList.contains('etaniAnimateId')) {
editAttribute(e.target, animateTypeUI, id, 'id');
} else if (e.target.classList.contains('etaniAnimateBegin')) {
editAttribute(e.target, animateTypeUI, id, 'begin');
} else if (e.target.classList.contains('etaniAnimateOther')) {
editAttribute(e.target, animateTypeUI, id, 'other', e.target.textContent.split('=')[0]);
}
});
const valueDiv = document.createElement('div');
valueDiv.className = 'etaniAnimateValue';
const avCtrlDiv = createControlButtons(animateTypeUI, id);
const avLabelSpan = document.createElement('span');
avLabelSpan.className = 'etaniAVLabel';
avLabelSpan.textContent = 'values : ';
const avDiv = document.createElement('div');
avDiv.className = 'etaniAV';
const avItemSpan = document.createElement('span');
avItemSpan.className = 'etaniAVItem';
avItemSpan.textContent = animateTypeUI === 'transform' ? 'a' : '1';
avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));
avDiv.appendChild(avItemSpan);
valueDiv.appendChild(avCtrlDiv);
valueDiv.appendChild(avLabelSpan);
valueDiv.appendChild(avDiv);
etaniAnimate.appendChild(valueDiv);
itemRight.appendChild(etaniAnimate);
// --- End of HTML control creation ---
updateEtaniResult();
}
```