正在做etani,ejtile的動畫套件
Re: 正在做etani,ejtile的動畫套件
代码: 全选
做一個js,功能描述如下:
一,在window.addEventListener(onload)之時,在div#etmainouter的下方增加
div.etaniouter>button#etanibutton{Animate it}
二,當按下etanibutton時,在etaniouter內部的最後增加:
div.etaniinner>(div.etaniCtrl+div.etaniCol+div.etaniResult)
三,同時,etaniCol內也增加多個div.etaniItem,它的數量等同於在.etdrop內<use/>的數量。
四,每個etaniItem內,包含了。etaniItemLeft和。etaniItemRight,etaniItemLeft的寬度固定為60px,其餘寬度給etaniItemRight佔滿。etaniItemLeft最小高度為48px。
五,etaniItemLeft的右邊有border 1 px,整個etaniItem也有border 1px,boxsizing要包含邊框。
六,所有html元素的增加,包括css的增加,都要用createElement的方式增加,而不是用文本方式增加。
只需要提供js,注釋全部使用英文。
代码: 全选
// Function to create and append the initial animation controls structure
window.addEventListener('load', () => {
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
// II. Add click listener to etanibutton
etanibutton.addEventListener('click', () => {
// Check if the inner structure already exists to prevent duplication
if (etaniouter.querySelector('.etaniinner')) {
console.log('Animation controls already created.');
return;
}
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Set styles for etaniCol to accommodate children and borders (part of V)
Object.assign(etaniCol.style, {
display: 'flex',
flexDirection: 'column',
gap: '5px', // Optional: space between items
padding: '5px' // Optional: internal padding
});
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III. Populate etaniCol with div.etaniItem based on the count of <use/> in .etdrop
const etdropElements = document.querySelectorAll('.etdrop use');
const itemCount = etdropElements.length;
for (let i = 0; i < itemCount; i++) {
// Create div.etaniItem (Part V: border 1px, box-sizing: border-box)
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
Object.assign(etaniItem.style, {
display: 'flex',
border: '1px solid #ccc', // V. border 1px
boxSizing: 'border-box', // V. box-sizing: border-box
width: '100%',
minHeight: '48px' // IV. Minimum height for item
});
// Create div.etaniItemLeft (IV. width: 60px, IV. min-height: 48px, V. right border 1px)
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
Object.assign(etaniItemLeft.style, {
width: '60px', // IV. width: 60px
minHeight: '48px', // IV. min-height: 48px
borderRight: '1px solid #ccc', // V. right border 1px
// Optional: Center content and set background for visibility
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f9f9f9'
});
etaniItemLeft.textContent = `L-${i+1}`; // Example content
// Create div.etaniItemRight (IV. fill remaining width)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
Object.assign(etaniItemRight.style, {
flexGrow: '1', // IV. fill remaining width
minHeight: '48px', // Ensures right side matches min height
padding: '5px' // Optional padding
});
etaniItemRight.textContent = `R-${i+1}`; // Example content
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
}
});
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup and prevention of duplication
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px; /* Example: add some separation from the element above */
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
display: flex;
flex-direction: column;
margin-top: 10px;
gap: 10px;
}
.etaniCol {
display: flex;
flex-direction: column;
gap: 5px; /* Optional: space between items */
padding: 5px; /* Optional: internal padding */
border: 1px solid #aaa; /* Optional: outline for the column */
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
display: flex;
border: 1px solid #ccc; /* V. border 1px for the entire item */
box-sizing: border-box; /* V. box-sizing: border-box */
width: 100%;
min-height: 48px; /* IV. Minimum height for item */
}
.etaniItemLeft {
width: 60px; /* IV. fixed width: 60px */
min-height: 48px; /* IV. min-height: 48px */
border-right: 1px solid #ccc; /* V. right border 1px */
/* Optional: Center content and set background for visibility */
display: flex;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
}
.etaniItemRight {
flex-grow: 1; /* IV. fill remaining width */
min-height: 48px; /* Ensures right side matches min height */
padding: 5px; /* Optional padding */
background-color: #fff;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Main function to create and append the initial animation controls structure
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter'; // Apply CSS class
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
// II. Add click listener to etanibutton
etanibutton.addEventListener('click', () => {
// Check if the inner structure already exists to prevent duplication
if (etaniouter.querySelector('.etaniinner')) {
console.log('Animation controls already created.');
return;
}
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner'; // Apply CSS class
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl'; // Apply CSS class
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol'; // Apply CSS class
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult'; // Apply CSS class
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III. Populate etaniCol with div.etaniItem based on the count of <use/> in .etdrop
const etdropElements = document.querySelectorAll('.etdrop use');
const itemCount = etdropElements.length;
for (let i = 0; i < itemCount; i++) {
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem'; // Apply CSS class
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft'; // Apply CSS class
etaniItemLeft.textContent = `L-${i+1}`; // Example content
// Create div.etaniItemRight
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight'; // Apply CSS class
etaniItemRight.textContent = `R-${i+1}`; // Example content
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
}
});
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
增加功能:
一,當etanibutton被按下時,它的innerHTML將改為「Animate finished」。
如果「Animate finished」不符合英文表達習慣,請幫我換個詞。
二,當再次按下etanibutton時,etaniinner將被刪除,裡面如果含有事件綁定也被解除綁定。
三,所有的css都不要用到flex,也不要用到table,改為其它方法。比如float:left和margin-left。
四,提取出.etdrop內每一個<use/>的href,賦值至tileid。
五,找到.etwait的內部的每一個<g>中的 <use href="tileid">,這些<use href="tileid">的transform都會含有translate、scale和rotate。將這些<use href="tileid">的transform賦值至etwaittransform,再把etwaittransform中的translate改為translate(20,20),scale和rotate保持不變。同時把<use href="tileid">的fill屬性賦值至etwaitfill。
六,再找到<defs>內部的每一個<g id="tileid">,克隆為tileclone,為tileclone加上transform=etwaittransform和fill=etwaitfill,去掉id,再套上一個svg的殼(長寬40,不要有viewbox,要有version、xmlns和xmlns:svg),加上.etanitileimg類名,之後放到每一個.etaniItemLeft中,並居中顯示。
七,在etanitileimg下方增加div.tileid,它的內容即是tileid。同樣居中顯示。
請用繁體中文回覆。
代码: 全选
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
/* Use margin to create vertical spacing instead of flex gap */
}
.etaniCtrl, .etaniCol, .etaniResult {
/* Since we are not using flex, these will stack naturally */
margin-bottom: 10px;
clear: both; /* Ensure they start on a new line after floats if necessary */
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto; /* To contain floated etaniItemLeft and etaniItemRight */
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc; /* V. border 1px for the entire item */
box-sizing: border-box; /* V. box-sizing: border-box */
width: 100%;
margin-bottom: -1px; /* Overlap borders to prevent double thickness */
overflow: auto; /* To contain floated elements */
}
.etaniItemLeft {
float: left; /* III. Use float instead of flex */
width: 60px; /* IV. fixed width: 60px */
min-height: 48px; /* IV. min-height: 48px */
border-right: 1px solid #ccc; /* V. right border 1px */
/* Center content vertically and horizontally */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0; /* Add a little internal padding */
}
.etaniItemRight {
/* III. Use margin-left to fill remaining width */
margin-left: 60px; /* Width of etaniItemLeft */
min-height: 48px;
padding: 5px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block; /* Ensure it takes up its own line */
margin: 0 auto; /* Center the SVG */
}
/* VII. Styles for tileid text */
.tileid {
text-align: center; /* Center the text */
font-size: 10px;
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
// Get all <g> elements within .etwait
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1); // Remove the '#' prefix
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
let groupElement = null;
// V. Search for the <use> element inside all <g> elements in .etwait
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
groupElement = group; // Record the parent group
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
if (targetUse) {
// V. Get the original transform and fill
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || 'none';
// V. Modify the transform: translate(20,20), keep scale and rotate
// Find scale(X) and rotate(Y) parts
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
// Reconstruct transform: new translate + old scale + old rotate
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' '); // Clean up spaces
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
etaniItemRight.textContent = `Tile ID: ${tileid}, Transform: ${etwaittransform}`; // Example content
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id'); // Remove id
// Set the new transform and fill
if (etwaittransform) {
tileclone.setAttribute('transform', etwaittransform);
}
if (etwaitfill) {
// Apply fill to the group clone
tileclone.setAttribute('fill', etwaitfill);
// Note: The fill should ideally be applied to paths/shapes inside the group,
// but setting on the <g> often works or is a starting point.
}
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg'; // Add CSS class
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
// Apply margin-left offset for etaniItemRight
etaniItemRight.style.marginLeft = '60px'; // Redundant if using CSS class, but safer here if class is not loaded
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Stop Animation';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Stop Animation') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
}
}
// Main function on window load
window.addEventListener('load', () => {
// III. Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener using the new toggle function
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
更改:
把<use href="tileid">的stroke和stroke-width屬性也跟fill屬性一樣處理。
用繁體中文回覆。
代码: 全选
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
/* Use margin to create vertical spacing instead of flex gap */
}
.etaniCtrl, .etaniCol, .etaniResult {
/* Since we are not using flex, these will stack naturally */
margin-bottom: 10px;
clear: both; /* Ensure they start on a new line after floats if necessary */
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto; /* To contain floated etaniItemLeft and etaniItemRight */
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc; /* V. border 1px for the entire item */
box-sizing: border-box; /* V. box-sizing: border-box */
width: 100%;
margin-bottom: -1px; /* Overlap borders to prevent double thickness */
overflow: auto; /* To contain floated elements */
}
.etaniItemLeft {
float: left; /* III. Use float instead of flex */
width: 60px; /* IV. fixed width: 60px */
min-height: 48px; /* IV. min-height: 48px */
border-right: 1px solid #ccc; /* V. right border 1px */
/* Center content vertically and horizontally */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0; /* Add a little internal padding */
}
.etaniItemRight {
/* III. Use margin-left to fill remaining width */
margin-left: 60px; /* Width of etaniItemLeft */
min-height: 48px;
padding: 5px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block; /* Ensure it takes up its own line */
margin: 0 auto; /* Center the SVG */
}
/* VII. Styles for tileid text */
.tileid {
text-align: center; /* Center the text */
font-size: 10px;
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
// Get all <g> elements within .etwait
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1); // Remove the '#' prefix
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
// V. Search for the <use> element inside all <g> elements in .etwait
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = ''; // New variable for stroke
let etwaitstrokeWidth = ''; // New variable for stroke-width
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || ''; // Get stroke
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || ''; // Get stroke-width
// V. Modify the transform: translate(20,20), keep scale and rotate
// Find scale(X) and rotate(Y) parts
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
// Reconstruct transform: new translate + old scale + old rotate
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' '); // Clean up spaces
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
etaniItemRight.textContent = `Tile ID: ${tileid}, Transform: ${etwaittransform}`; // Example content
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id'); // Remove id
// Set the new transform
if (etwaittransform) {
tileclone.setAttribute('transform', etwaittransform);
}
// Set the fill, stroke, and stroke-width (處理新增的屬性)
if (etwaitfill) {
tileclone.setAttribute('fill', etwaitfill);
}
if (etwaitstroke) {
tileclone.setAttribute('stroke', etwaitstroke);
}
if (etwaitstrokeWidth) {
tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
}
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg'; // Add CSS class
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
// Apply margin-left offset for etaniItemRight
etaniItemRight.style.marginLeft = '60px';
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Stop Animation';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Stop Animation') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
}
}
// Main function on window load
window.addEventListener('load', () => {
// III. Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener using the new toggle function
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
etaniItemRight.style.marginLeft = '60px';
四,在div.etaniResult內含有:
img.etaniResultImage
a.etaniResultDownload
a.etaniResultRename
span.etaniResultSize
五,當點擊button#etanistart,js將執行以下操作:
5.1把當前svg#etmain的clone賦值給變量etani_clone。
5.2把etani_clone的.etwait賦值給etani_wait_clone,並把.etwait從etani_clone中刪掉。
5.3把etani_clone的.etdrop的transform設置為translate(240,240) scale(1,1),之後以base64的方式顯示至.etaniResultImage,同時span.etaniResultSize也顯示它的大小(byte),a.etaniResultDownload點擊就可以下載,a.etaniResultRename點擊就重命名所下載的文件名(點認文件名為ejtileAnimation_yyyymmdd_hhiiss.svg)。往後只要etani_clone更改,這些都要即時更新。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,把「etaniItemRight.style.marginLeft = '60px'; 」去掉。
二,etaniItemRight的內容清空。
三,把「Stop Animation」改為「Close Ejtile Ani」。
四,在div.etaniResult內增加:
img.etaniResultImage+a.etaniResultDownload+a.etaniResultRename+span.etaniResultSize
其中etaniResultImage單獨一行居中,最大寬度為480px,當屏幕寬度小於480px,它就跟屏幕一樣寬。css不要用flex。
五,當按下etanibutton時,js將執行以下操作:
5.1把當前svg#etmain的clone賦值給變量etani_clone。
5.2把etani_clone中的.etwait刪掉。
5.3把etani_clone的id改為"etmainani",之後以base64的方式顯示至.etaniResultImage,同時span.etaniResultSize也顯示它的大小(byte),a.etaniResultDownload點擊就可以下載,a.etaniResultRename點擊就重命名所下載的文件名(點認文件名為ejtileAnimation_yyyymmdd_hhiiss.svg)。
往後只要etani_clone更改,這些都要即時更新。
用繁體中文回覆。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl, .etaniCol {
margin-bottom: 10px;
clear: both;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
}
/* IV. Styles for etaniResult container and its elements */
.etaniResult {
text-align: center; /* Center content like image and buttons */
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* IV. etaniResultImage CSS: centered, max-width 480px, responsive */
.etaniResultImage {
display: block; /* Ensure it takes up its own line for centering */
max-width: 480px; /* Maximum width */
width: 100%; /* Becomes same width as screen if screen < 480px */
height: auto;
margin: 0 auto 10px auto; /* Center it, add bottom margin */
border: 1px solid #000; /* Optional: for visibility */
}
.etaniResultDownload, .etaniResultRename {
margin: 0 5px;
display: inline-block;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 0.8em;
color: #555;
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
/* III. Now rely solely on float clearance and margin-left offset for content flow */
min-height: 48px;
background-color: #fff;
/* The element will naturally take up the remaining space if not floated,
but we use min-height and proper float management for the container. */
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block;
margin: 0 auto;
}
/* VII. Styles for tileid text */
.tileid {
text-align: center;
font-size: 10px;
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etaniClone state
function updateEtaniResult() {
if (!etaniClone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
// Ensure it ends with .svg if not specified
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult (IV. Add result elements)
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// IV. Add img, a, span elements
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
etaniResult.appendChild(resultImg);
etaniResult.appendChild(document.createElement('br')); // Force buttons/span to new line
etaniResult.appendChild(downloadLink);
etaniResult.appendChild(renameLink);
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1);
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
// V. Modify the transform
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight (II. Content is cleared)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
// etaniItemRight.textContent = ''; // Already empty
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
// Set the new transform and attributes
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
// I. etaniItemRight.style.marginLeft = '60px'; is removed
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I, III. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Close Ejtile Ani';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etaniClone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
border: 1px solid blue;
color: blue;
}
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,css加上:
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
border: 1px solid blue;
color: blue;
}
同時把這兩個元素放進一個父元素div.etaniResultDR中,使它們水平居中。
二,為etaniItemRight加上margin-left: 60,以及padding:7px。
用繁體中文回覆。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl, .etaniCol {
margin-bottom: 10px;
clear: both;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
}
/* IV. Styles for etaniResult container and its elements */
.etaniResult {
text-align: center; /* Center content like image and buttons */
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* New: Container for Download/Rename links for horizontal centering */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
/* New: Styles for Download and Rename links (要求一) */
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
border: 1px solid blue;
color: blue;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 0.8em;
color: #555;
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
/* 要求二:加上 margin-left 和 padding */
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block;
margin: 0 auto;
}
/* VII. Styles for tileid text */
.tileid {
text-align: center;
font-size: 10px;
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
// Use btoa for Base64 encoding
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etaniClone state
function updateEtaniResult() {
if (!etaniClone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
// Ensure it ends with .svg if not specified
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// IV. Add img element
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
// 要求一:創建父元素 div.etaniResultDR
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
// 將下載和重命名連結放入父元素
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
// 組合 etaniResult 內容
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv); // 放置居中容器
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1);
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
// V. Modify the transform
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight (II. Content is cleared)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
// Set the new transform and attributes
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I, III. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Close Ejtile Ani';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etaniClone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,把.etaniResultDownload和.etaniResultRename的字號改為16px,把.etaniResultRename的顏色改為brown(包括字體顏色和邊框)。
二,把.etaniResultSize和.tileid的字號改為12px。往後最小字號只能是12px,字號的單位只能用px。
用繁體中文回覆。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl, .etaniCol {
margin-bottom: 10px;
clear: both;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
}
/* IV. Styles for etaniResult container and its elements */
.etaniResult {
text-align: center; /* Center content like image and buttons */
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* Container for Download/Rename links for horizontal centering */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
/* New: Styles for Download and Rename links (要求一:字號16px) */
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px; /* 已修改為 16px */
}
/* New: .etaniResultDownload default style */
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
/* New: .etaniResultRename style (要求一:顏色改為 brown) */
.etaniResultRename {
border: 1px solid brown; /* 邊框顏色改為 brown */
color: brown; /* 字體顏色改為 brown */
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px; /* 已修改為 12px */
color: #555;
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block;
margin: 0 auto;
}
/* VII. Styles for tileid text */
.tileid {
text-align: center;
font-size: 12px; /* 已修改為 12px */
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
// Use btoa for Base64 encoding
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etaniClone state
function updateEtaniResult() {
if (!etaniClone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
// Ensure it ends with .svg if not specified
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// IV. Add img element
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
// 要求:創建父元素 div.etaniResultDR
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
// 將下載和重命名連結放入父元素
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
// 組合 etaniResult 內容
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv); // 放置居中容器
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1);
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
// V. Modify the transform
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight (II. Content is cleared)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
// Set the new transform and attributes
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I, III. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Close Ejtile Ani';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etaniClone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,在div.etaniCtrl中增加.etaniContent
二,在.etaniContent中增加a.etaniCenter{Center},當點擊它時,etani_clone中的.etdrop的transform就會改為translate(240,240) scale(1,1)。
用繁體中文回覆。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0; /* Optional: to frame the control area */
}
/* New: Styles for etaniContent (Flex is replaced by text-align for centering) */
.etaniContent {
text-align: center;
padding: 5px 0;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
margin-bottom: 10px;
clear: both;
}
/* IV. Styles for etaniResult container and its elements */
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* Container for Download/Rename links for horizontal centering */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
/* Styles for control links (like Center button) */
.etaniCenter {
display: inline-block;
margin: 0 5px;
text-decoration: none;
padding: 5px 10px;
border: 1px solid green;
color: green;
font-size: 14px; /* Default size for controls */
}
/* Styles for Download and Rename links (要求一:字號16px) */
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
}
/* .etaniResultDownload default style */
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
/* .etaniResultRename style (要求一:顏色改為 brown) */
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px; /* 最小字號 12px */
color: #555;
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block;
margin: 0 auto;
}
/* VII. Styles for tileid text */
.tileid {
text-align: center;
font-size: 12px; /* 最小字號 12px */
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etaniClone state
function updateEtaniResult() {
if (!etaniClone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
// Ensure it ends with .svg if not specified
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to handle the 'Center' button click
function handleCenterClick(e) {
e.preventDefault();
if (etaniClone) {
// 獲取 etani_clone 中的 .etdrop 元素
const etdrop = etaniClone.querySelector('.etdrop');
if (etdrop) {
// 設置新的 transform 屬性
etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
console.log("etdrop transform updated to: translate(240,240) scale(1,1)");
// 即時更新結果顯示
updateEtaniResult();
} else {
console.warn('.etdrop not found in the cloned SVG.');
}
} else {
console.warn('etaniClone is not initialized.');
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// 要求一:在 div.etaniCtrl 中增加 div.etaniContent
const etaniContent = document.createElement('div');
etaniContent.className = 'etaniContent';
// 要求二:在 .etaniContent 中增加 a.etaniCenter{Center}
const centerLink = document.createElement('a');
centerLink.className = 'etaniCenter';
centerLink.textContent = 'Center';
centerLink.href = '#';
centerLink.addEventListener('click', handleCenterClick);
// 組合 etaniCtrl 內容
etaniContent.appendChild(centerLink);
etaniCtrl.appendChild(etaniContent);
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// IV. Add img element
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
// 創建父元素 div.etaniResultDR
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
// 將下載和重命名連結放入父元素
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
// 組合 etaniResult 內容
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv); // 放置居中容器
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol (unchanged tile processing logic)
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1);
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
// V. Modify the transform
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight (II. Content is cleared)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
// Set the new transform and attributes
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I, III. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Close Ejtile Ani';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etaniClone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,在div.etaniCtrl中增加.etaniContent
二,在.etaniContent中增加a.etaniCenter{Center},當點擊它時,etani_clone中的.etdrop的transform就會改為translate(240,240) scale(1,1)。
用繁體中文回覆。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles'; // Give it an ID for easy lookup
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto; /* To contain floated elements */
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0; /* Optional: to frame the control area */
}
/* New: Styles for etaniContent (Flex is replaced by text-align for centering) */
.etaniContent {
text-align: center;
padding: 5px 0;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
margin-bottom: 10px;
clear: both;
}
/* IV. Styles for etaniResult container and its elements */
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* Container for Download/Rename links for horizontal centering */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
/* Styles for control links (like Center button) */
.etaniCenter {
display: inline-block;
margin: 0 5px;
text-decoration: none;
padding: 5px 10px;
border: 1px solid green;
color: green;
font-size: 14px; /* Default size for controls */
}
/* Styles for Download and Rename links (要求一:字號16px) */
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
}
/* .etaniResultDownload default style */
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
/* .etaniResultRename style (要求一:顏色改為 brown) */
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px; /* 最小字號 12px */
color: #555;
}
/* III, IV, V. Styles for etaniItem, etaniItemLeft, etaniItemRight */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
/* VI. Styles for the cloned SVG tile */
.etanitileimg {
display: block;
margin: 0 auto;
}
/* VII. Styles for tileid text */
.tileid {
text-align: center;
font-size: 12px; /* 最小字號 12px */
word-break: break-all;
padding-top: 2px;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etaniClone state
function updateEtaniResult() {
if (!etaniClone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
// Ensure it ends with .svg if not specified
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to handle the 'Center' button click
function handleCenterClick(e) {
e.preventDefault();
if (etaniClone) {
// 獲取 etani_clone 中的 .etdrop 元素
const etdrop = etaniClone.querySelector('.etdrop');
if (etdrop) {
// 設置新的 transform 屬性
etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
console.log("etdrop transform updated to: translate(240,240) scale(1,1)");
// 即時更新結果顯示
updateEtaniResult();
} else {
console.warn('.etdrop not found in the cloned SVG.');
}
} else {
console.warn('etaniClone is not initialized.');
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// 要求一:在 div.etaniCtrl 中增加 div.etaniContent
const etaniContent = document.createElement('div');
etaniContent.className = 'etaniContent';
// 要求二:在 .etaniContent 中增加 a.etaniCenter{Center}
const centerLink = document.createElement('a');
centerLink.className = 'etaniCenter';
centerLink.textContent = 'Center';
centerLink.href = '#';
centerLink.addEventListener('click', handleCenterClick);
// 組合 etaniCtrl 內容
etaniContent.appendChild(centerLink);
etaniCtrl.appendChild(etaniContent);
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// IV. Add img element
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
// 創建父元素 div.etaniResultDR
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
// 將下載和重命名連結放入父元素
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
// 組合 etaniResult 內容
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv); // 放置居中容器
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol (unchanged tile processing logic)
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// IV. Get tileid
const tileid = useElement.getAttribute('href').substring(1);
// Find the corresponding <use> element inside .etwait for this tileid
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
// V. Get the original transform, fill, stroke, and stroke-width
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
// V. Modify the transform
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
// VI. Find the original <g> in <defs> and clone it
const originalTile = document.querySelector(`defs g#${tileid}`);
// Create div.etaniItem
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
// Create div.etaniItemLeft
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
// Create div.etaniItemRight (II. Content is cleared)
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
// VI. Clone, modify, wrap, and append SVG
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
// Set the new transform and attributes
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
// Create the SVG wrapper
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
// VII. Add div.tileid below the SVG wrapper
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
// Append Left and Right to Item
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
// Append Item to etaniCol
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
// I, III. Check current state and toggle
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
// 1. Change button text
etanibutton.textContent = 'Close Ejtile Ani';
// 2. Create and insert etaniinner
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF (II. etaniinner will be removed)
// 1. Change button text
etanibutton.textContent = 'Animate it';
// 2. Remove etaniinner and all its contents (II. Event listeners inside are automatically unbound)
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etaniClone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
// Call the function to add all necessary CSS dynamically
addDynamicStyles();
// I. Add div.etaniouter>button#etanibutton{Animate it} below div#etmainouter
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
// Create div.etaniouter
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
// Create button#etanibutton{Animate it}
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
// II. Add click listener
etanibutton.addEventListener('click', toggleAnimation);
// Append button to etaniouter
etaniouter.appendChild(etanibutton);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
<animateTransform attributeName="transform" attributeType="XML" type="translate" values="0" dur="1s" fill="freeze" additive="sum"/>
<animateTransform attributeName="transform" attributeType="XML" type="scale" values="0" dur="1s" fill="freeze" additive="sum"/>
<animateTransform attributeName="transform" attributeType="XML" type="rotate" values="0" dur="1s" fill="freeze" additive="sum"/>
repeatCount="indefinite"
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
改動:
一,在div.etaniCtrl中增加.etaniSetting和.etaniAllAppend。它們和.etaniContent的css-display都是inline-block。帶灰色邊框。帶上label文字。
二,在.etaniSetting中增加radio:.etaniSettingMode.etaniSettingFreeze{freeze}和.etaniSettingMode.etaniSettingRepeat{repeat}。默認選freeze。樣式要美觀,不要用html原生的。
三,在.etaniAllAppend中增加兩個button:.etaniAllAppendTransform{transform},etaniAllAppendOpacity{opacity}。
四,當按下.etaniAllAppendTransform時,js將做以下操作:
4.1,每個.etaniItemRight都增加一個.etaniAnimate。(它將三個標籤合為一個.etaniAnimate來表示)
.etaniAnimate內包含:
span.etaniAnimateName+
span.etaniAnimateDur+
div.etaniAnimateValue
其中.etaniAnimateName是灰底白字,內容是「Transform」。
.etaniAnimateDur的innerHTML為「dur: n」,n指一個數字,也有可能是小數。
div.etaniAnimateValue的內含「span.etaniAVAdd+span.etaniAVLabel{values : }+div.etaniAV」。
div.etaniAV中可能含有一個或多個span.etaniAVItem,起初只含有一個。
每個span.etaniAVItem的長寬為24px,背景為暗橙色,邊框為虛線天藍色,左右相距5px。
span.etaniAVAdd的長寬為24px,背景為淺綠色,邊框為細線近淺綠色,與右相距5px,內容為一個加號的svg。
4.2,在etani_clone中,.etdrop裡面的<use/>內都要添加三個animateTransform標籤:
<animateTransform attributeName="transform" attributeType="XML" type="translate" values="" dur="1s" fill="freeze"/>
<animateTransform attributeName="transform" attributeType="XML" type="scale" values="" dur="1s" fill="freeze"/>
<animateTransform attributeName="transform" attributeType="XML" type="rotate" values="" dur="1s" fill="freeze"/>
它們values的初始值是當前的svg#etmain中的.etdrop中對應的<use/>的transform="translate(…) scale(…) rotate(…)"的值。
五,當.etaniSettingMode切換為repeat時,所有的fill="freeze"都替換為repeatCount="indefinite"。
六,當點擊etaniAVAdd時,etani_clone中所對應的<use/>中的三個<animateTransform>的values都增加一個value,這個value是當前的svg#etmain中對應的<use/>的transform的值。
七,在etaniContent中,在最前面新增a.etaniContentHTML,當點擊時,頁面將彈出一個居中的div,css為fixed,寬度是98%,高度是48%。內含一個textarea,內容是etani_clone的outerHTML。同時還包含關閉圖標在右上角。
八,前面的代碼中,etani_clone被寫成etaniClone,現在要改回etani_clone。
用繁體中文回覆。在代碼中所有注釋都要用英文。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained (renamed to etani_clone)
let etani_clone = null;
// Function to dynamically add a <style> element with all necessary CSS rules
function addDynamicStyles() {
// Check if styles are already present to avoid duplication
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles';
styleSheet.textContent = `
/* General Containers */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto;
}
.etaniinner {
margin-top: 10px;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
margin-bottom: 10px;
clear: both;
}
/* Control Area - New and Modified */
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0;
text-align: left; /* Default alignment for blocks */
overflow: auto;
}
.etaniContent, .etaniSetting, .etaniAllAppend {
display: inline-block; /* 讓它們水平排列 */
margin-right: 15px;
vertical-align: top;
padding: 5px 10px;
border: 1px solid #ccc; /* 灰色邊框 */
}
.etaniCtrl label {
display: block;
font-weight: bold;
margin-bottom: 5px;
font-size: 14px;
}
/* II. Radio Button Styles (Custom look, not native) */
.etaniSettingMode {
display: inline-block;
margin-right: 10px;
cursor: pointer;
font-size: 14px;
padding: 3px 5px;
border: 1px solid #ddd;
background-color: #eee;
user-select: none;
}
.etaniSettingMode.selected {
background-color: #99ccff;
border-color: #3399ff;
font-weight: bold;
}
/* III. Append Buttons */
.etaniAllAppend button {
display: block; /* Stack vertically */
width: 100%;
margin-bottom: 5px;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
}
/* IV. etaniAnimate Component Styles */
.etaniAnimate {
border: 1px solid #666;
margin-top: 5px;
padding: 5px;
font-size: 12px; /* Base font size for component */
}
.etaniAnimateName {
background-color: gray;
color: white;
padding: 2px 4px;
margin-right: 5px;
font-size: 12px;
}
.etaniAnimateDur {
margin-right: 10px;
font-size: 12px;
}
.etaniAnimateValue {
margin-top: 5px;
clear: both;
overflow: auto;
}
.etaniAVLabel {
margin-right: 5px;
font-size: 12px;
}
.etaniAV {
display: inline-block;
vertical-align: middle;
}
/* IV. .etaniAVItem and .etaniAVAdd Styles */
.etaniAVAdd, .etaniAVItem {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px; /* Center content vertically */
text-align: center;
cursor: pointer;
vertical-align: middle;
margin-right: 5px;
box-sizing: border-box;
position: relative;
}
.etaniAVItem {
background-color: #ff8c00; /* Dark Orange */
border: 1px dashed #00bfff; /* Dashed Sky Blue */
}
.etaniAVAdd {
background-color: #90ee90; /* Light Green */
border: 1px solid #66cdaa; /* Light Sea Green (close to light green) */
}
/* Plus SVG inside .etaniAVAdd */
.etaniAVAdd svg {
width: 100%;
height: 100%;
vertical-align: top;
}
/* Result Area */
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
.etaniResultDownload, .etaniResultRename, .etaniCenter, .etaniContentHTML {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
cursor: pointer;
}
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniCenter {
border: 1px solid green;
color: green;
}
/* New: etaniContentHTML style */
.etaniContentHTML {
border: 1px solid purple;
color: purple;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px;
color: #555;
}
/* Item structure */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
.tileid {
text-align: center;
font-size: 12px;
word-break: break-all;
padding-top: 2px;
}
/* VII. Modal/Popup Styles */
.etani-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.etani-modal-content {
background: white;
width: 98%;
height: 48vh; /* 48% height of viewport */
position: relative;
padding: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
border: 1px solid #000;
box-sizing: border-box;
}
.etani-modal-content textarea {
width: 100%;
height: calc(100% - 30px); /* Adjust height for button/padding */
font-family: monospace;
font-size: 12px;
border: none;
resize: none;
box-sizing: border-box;
}
.etani-modal-close {
position: absolute;
top: 5px;
right: 10px;
font-size: 24px;
cursor: pointer;
color: #333;
}
`;
// Append the <style> element to the document's <head>
document.head.appendChild(styleSheet);
}
// Function to encode SVG string to Base64 data URL
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
// Function to format byte size (for span.etaniResultSize)
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Function to update the etaniResult section with the current etani_clone state
function updateEtaniResult() {
if (!etani_clone) return;
// Get the SVG string and its size
const svgString = new XMLSerializer().serializeToString(etani_clone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Format the date for the download filename
const now = new Date();
const yyyy = String(now.getFullYear());
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
// Get result elements
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
// 5.3 Update Image
imgElement.src = base64Url;
// 5.3 Update Size
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
// 5.3 Update Download Link
downloadElement.href = base64Url;
// Only update download name if it hasn't been renamed by user
if (!downloadElement.download || downloadElement.download.startsWith('ejtileAnimation_')) {
downloadElement.download = defaultFilename;
}
// 5.3 Rename Link (Handles renaming logic)
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Function to get initial transform values from the original SVG
function getInitialTransformValues(useElement) {
const transform = useElement.getAttribute('transform') || '';
// Default values if parts are missing
let translateValue = 'translate(0,0)';
let scaleValue = 'scale(1)';
let rotateValue = 'rotate(0)';
// Extract values
const translateMatch = transform.match(/translate\(([^)]+)\)/);
const scaleMatch = transform.match(/scale\(([^)]+)\)/);
const rotateMatch = transform.match(/rotate\(([^)]+)\)/);
if (translateMatch) {
translateValue = `translate(${translateMatch[1]})`;
}
if (scaleMatch) {
scaleValue = `scale(${scaleMatch[1]})`;
}
if (rotateMatch) {
rotateValue = `rotate(${rotateMatch[1]})`;
}
return {
translate: translateValue,
scale: scaleValue,
rotate: rotateValue,
// The combined string for the full transform attribute value
combined: `${translateValue} ${scaleValue} ${rotateValue}`.trim().replace(/\s+/g, ' ')
};
}
// Function to handle the 'Center' button click
function handleCenterClick(e) {
e.preventDefault();
if (etani_clone) {
const etdrop = etani_clone.querySelector('.etdrop');
if (etdrop) {
etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
updateEtaniResult();
}
}
}
// Function to handle the 'Show HTML' button click (Requirement VII)
function handleShowHtmlClick(e) {
e.preventDefault();
if (!etani_clone) {
alert("Animation is not active (etani_clone is null).");
return;
}
const svgString = new XMLSerializer().serializeToString(etani_clone);
// Create Modal/Popup structure
const overlay = document.createElement('div');
overlay.className = 'etani-modal-overlay';
const modalContent = document.createElement('div');
modalContent.className = 'etani-modal-content';
const closeIcon = document.createElement('span');
closeIcon.className = 'etani-modal-close';
closeIcon.innerHTML = '×'; // 'x' symbol
closeIcon.onclick = () => overlay.remove(); // Close modal on click
const textarea = document.createElement('textarea');
textarea.value = svgString;
modalContent.appendChild(closeIcon);
modalContent.appendChild(textarea);
overlay.appendChild(modalContent);
document.body.appendChild(overlay);
textarea.focus();
}
// Function to handle the radio button change (Requirement V)
function handleModeChange(e) {
const mode = e.currentTarget.textContent;
const selectedClass = 'selected';
// Update visual selection
document.querySelectorAll('.etaniSettingMode').forEach(radio => {
radio.classList.remove(selectedClass);
});
e.currentTarget.classList.add(selectedClass);
if (etani_clone) {
const animateTransforms = etani_clone.querySelectorAll('animateTransform');
animateTransforms.forEach(animate => {
if (mode === 'repeat') {
// V. When repeat is selected, set repeatCount="indefinite" and remove fill="freeze"
animate.setAttribute('repeatCount', 'indefinite');
animate.removeAttribute('fill');
} else if (mode === 'freeze') {
// V. When freeze is selected, set fill="freeze" and remove repeatCount
animate.setAttribute('fill', 'freeze');
animate.removeAttribute('repeatCount');
}
});
// Update the result display immediately
updateEtaniResult();
}
}
// Function to handle adding a new value (Requirement VI)
function handleAddValueClick(e) {
e.preventDefault();
const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
const itemIndex = Array.from(etaniItemRight.parentNode.parentNode.children).indexOf(etaniItemRight.parentNode);
if (etani_clone) {
// Find the corresponding original <use/> to get the current value
const originalDropUses = document.querySelectorAll('.etdrop use');
const currentOriginalUse = originalDropUses[itemIndex];
if (currentOriginalUse) {
// Get the full transform value from the original <use/> (V. initial values)
const values = getInitialTransformValues(currentOriginalUse);
const currentValue = values.combined;
// Find the corresponding <use/> in the etani_clone
const cloneDropUses = etani_clone.querySelectorAll('.etdrop use');
const cloneUse = cloneDropUses[itemIndex];
if (cloneUse) {
// VI. Append the new value to the three animateTransform tags
const animates = cloneUse.querySelectorAll('animateTransform');
animates.forEach(animate => {
let currentValues = animate.getAttribute('values') || '';
if (currentValues) {
currentValues += `;${currentValue}`;
} else {
currentValues = currentValue; // Should not happen if transform is applied correctly
}
animate.setAttribute('values', currentValues);
});
// Add the visual indicator
const etaniAV = e.currentTarget.closest('.etaniAV');
const newItem = document.createElement('span');
newItem.className = 'etaniAVItem';
etaniAV.appendChild(newItem);
// Update the result display immediately
updateEtaniResult();
}
}
}
}
// Function to create the .etaniAnimate structure (Requirement IV.1)
function createAnimateStructure() {
const animateDiv = document.createElement('div');
animateDiv.className = 'etaniAnimate';
// Name
const nameSpan = document.createElement('span');
nameSpan.className = 'etaniAnimateName';
nameSpan.textContent = 'Transform';
// Duration (dur: n) - Placeholder for now
const durSpan = document.createElement('span');
durSpan.className = 'etaniAnimateDur';
durSpan.innerHTML = 'dur: 1s';
// Value Container
const valueDiv = document.createElement('div');
valueDiv.className = 'etaniAnimateValue';
// Add button (IV.1)
const addSpan = document.createElement('span');
addSpan.className = 'etaniAVAdd';
// Add plus SVG
addSpan.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>`;
addSpan.addEventListener('click', handleAddValueClick); // VI. Bind click event
// Label
const labelSpan = document.createElement('span');
labelSpan.className = 'etaniAVLabel';
labelSpan.textContent = 'values : ';
// Values holder
const avDiv = document.createElement('div');
avDiv.className = 'etaniAV';
// Initial Item (IV.1)
const initialItem = document.createElement('span');
initialItem.className = 'etaniAVItem';
avDiv.appendChild(initialItem);
valueDiv.appendChild(addSpan);
valueDiv.appendChild(labelSpan);
valueDiv.appendChild(avDiv);
animateDiv.appendChild(nameSpan);
animateDiv.appendChild(durSpan);
animateDiv.appendChild(valueDiv);
return animateDiv;
}
// Function to handle the 'Append Transform' button click (Requirement IV)
function handleAllAppendTransformClick(e) {
e.preventDefault();
// IV.1 Add visual structure to all .etaniItemRight
document.querySelectorAll('.etaniItemRight').forEach(rightDiv => {
// Prevent duplication
if (!rightDiv.querySelector('.etaniAnimate')) {
rightDiv.appendChild(createAnimateStructure());
}
});
// IV.2 Add animateTransform tags to etani_clone
if (etani_clone) {
const cloneDropUses = etani_clone.querySelectorAll('.etdrop use');
const originalDropUses = document.querySelectorAll('.etdrop use');
cloneDropUses.forEach((cloneUse, i) => {
const originalUse = originalDropUses[i];
// Get initial values (V. initial values)
const transforms = getInitialTransformValues(originalUse);
const initialValue = transforms.combined;
// Function to create and append <animateTransform>
const createAnimate = (type) => {
// Initial values attribute should contain the starting transform (itself)
const values = initialValue;
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
animate.setAttribute('attributeName', 'transform');
animate.setAttribute('attributeType', 'XML');
animate.setAttribute('type', type);
animate.setAttribute('values', values); // Initial value
animate.setAttribute('dur', '1s');
animate.setAttribute('fill', 'freeze'); // Default mode
return animate;
};
// Remove any existing transforms before appending
cloneUse.querySelectorAll('animateTransform').forEach(anim => anim.remove());
// Append the three animateTransform tags
cloneUse.appendChild(createAnimate('translate'));
cloneUse.appendChild(createAnimate('scale'));
cloneUse.appendChild(createAnimate('rotate'));
});
// Update the result display immediately
updateEtaniResult();
}
}
// Function to handle the creation and population of etaniinner
function createEtaniInner(etaniouter) {
// 5.1 Clone the original SVG
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
// VIII. Use etani_clone
etani_clone = originalSvg.cloneNode(true);
// 5.2 Remove .etwait from the clone
const etwaitElement = etani_clone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
// 5.3 Update clone ID
etani_clone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// I. Create .etaniSetting and .etaniAllAppend
const etaniSetting = document.createElement('div');
etaniSetting.className = 'etaniSetting';
etaniSetting.innerHTML = `<label>Animation Mode</label>`;
const etaniAllAppend = document.createElement('div');
etaniAllAppend.className = 'etaniAllAppend';
etaniAllAppend.innerHTML = `<label>Batch Append</label>`;
// I, II. Add radio buttons to .etaniSetting
const freezeRadio = document.createElement('span');
freezeRadio.className = 'etaniSettingMode etaniSettingFreeze selected'; // Default selected
freezeRadio.textContent = 'freeze';
freezeRadio.addEventListener('click', handleModeChange);
const repeatRadio = document.createElement('span');
repeatRadio.className = 'etaniSettingMode etaniSettingRepeat';
repeatRadio.textContent = 'repeat';
repeatRadio.addEventListener('click', handleModeChange);
etaniSetting.appendChild(freezeRadio);
etaniSetting.appendChild(repeatRadio);
// I, III. Add buttons to .etaniAllAppend
const transformButton = document.createElement('button');
transformButton.className = 'etaniAllAppendTransform';
transformButton.textContent = 'Transform';
transformButton.addEventListener('click', handleAllAppendTransformClick); // IV. Bind click
const opacityButton = document.createElement('button');
opacityButton.className = 'etaniAllAppendOpacity';
opacityButton.textContent = 'Opacity';
etaniAllAppend.appendChild(transformButton);
etaniAllAppend.appendChild(opacityButton);
// I, VII. Create .etaniContent
const etaniContent = document.createElement('div');
etaniContent.className = 'etaniContent';
etaniContent.innerHTML = `<label>Controls</label>`;
// VII. Add a.etaniContentHTML
const htmlLink = document.createElement('a');
htmlLink.className = 'etaniContentHTML';
htmlLink.textContent = 'Show HTML';
htmlLink.href = '#';
htmlLink.addEventListener('click', handleShowHtmlClick); // VII. Bind click
// Combine etaniCtrl content (order: HTML, Center, Setting, Append)
etaniContent.appendChild(htmlLink);
// Center link logic (from previous requirement)
const centerLink = document.createElement('a');
centerLink.className = 'etaniCenter';
centerLink.textContent = 'Center';
centerLink.href = '#';
centerLink.addEventListener('click', handleCenterClick);
etaniContent.appendChild(centerLink);
etaniCtrl.appendChild(etaniContent);
etaniCtrl.appendChild(etaniSetting);
etaniCtrl.appendChild(etaniAllAppend);
// Create div.etaniCol
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// Result elements (unchanged)
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv);
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// Populate etaniCol (tile structure)
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
const tileid = useElement.getAttribute('href').substring(1);
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '';
let etwaitfill = '';
let etwaitstroke = '';
let etwaitstrokeWidth = '';
if (targetUse) {
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
const originalTile = document.querySelector(`defs g#${tileid}`);
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
etaniCol.appendChild(etaniItem);
});
// Update the result section immediately after etaniinner is fully created
updateEtaniResult();
}
// Function to handle button click: toggle animation controls
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner'); // Find the inner container
if (etanibutton.textContent === 'Animate it') {
// State: Animation is OFF -> Turn ON
etanibutton.textContent = 'Close Ejtile Ani';
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
// State: Animation is ON -> Turn OFF
etanibutton.textContent = 'Animate it';
if (etaniinner) {
etaniinner.remove();
}
// Reset global clone variable
etani_clone = null;
}
}
// Main function on window load
window.addEventListener('load', () => {
addDynamicStyles();
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
etanibutton.addEventListener('click', toggleAnimation);
etaniouter.appendChild(etanibutton);
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在做etani,ejtile的動畫套件
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etaniClone = null;
// --- CSS STYLES ---
function addDynamicStyles() {
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles';
styleSheet.textContent = `
/* I. Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto;
}
/* II. Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0;
text-align: center; /* To center etaniContent, etaniSetting, etaniAllAppend */
}
/* 要求一:etaniContent, etaniSetting, etaniAllAppend 樣式 */
.etaniContent, .etaniSetting, .etaniAllAppend {
display: inline-block;
vertical-align: top;
padding: 5px;
border: 1px solid #ccc; /* 灰色邊框 */
margin: 0 5px 5px 5px;
text-align: left;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
margin-bottom: 10px;
clear: both;
}
/* IV. Styles for etaniResult */
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* ------------------ CONTROL & BUTTON STYLES ------------------ */
/* Styles for control links (Center button) */
.etaniContent a {
display: inline-block;
margin: 0 5px;
text-decoration: none;
padding: 5px 10px;
font-size: 14px;
}
.etaniCenter {
border: 1px solid green;
color: green;
}
.etaniContentHTML {
border: 1px solid #0099ff;
color: #0099ff;
margin-right: 15px;
}
/* 要求三:etaniAllAppend buttons */
.etaniAllAppend button {
padding: 5px 10px;
font-size: 16px;
margin: 0 5px;
cursor: pointer;
border: 1px solid #333;
background-color: #fff;
}
/* 要求二:自定義 Radio 按鈕樣式 */
.etaniSettingMode {
display: inline-block;
cursor: pointer;
padding: 4px 8px;
margin: 0 3px;
font-size: 14px;
border: 1px solid #888;
background-color: #eee;
color: #333;
user-select: none;
}
.etaniSettingMode.active {
background-color: #008CBA;
color: white;
border-color: #008CBA;
}
/* ------------------ RESULT & ANIMAION ITEM STYLES ------------------ */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
/* 要求一:下載和重命名連結字號16px */
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
}
/* .etaniResultDownload default style */
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
/* .etaniResultRename style (要求一:顏色改為 brown) */
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
/* 要求二:最小字號 12px */
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px;
color: #555;
}
/* etaniItem structure (Float layout) */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
border-right: 1px solid #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
/* 要求二:最小字號 12px */
.tileid {
text-align: center;
font-size: 12px;
word-break: break-all;
padding-top: 2px;
}
/* ------------------ ANIMATE CONTROLS (4.1) ------------------ */
.etaniAnimate {
border: 1px solid #999;
padding: 5px;
margin-bottom: 5px;
}
.etaniAnimateName {
display: inline-block;
padding: 2px 5px;
background-color: #555; /* 灰底 */
color: white; /* 白字 */
margin-right: 10px;
font-size: 12px; /* 最小字號 12px */
}
.etaniAnimateDur {
display: inline-block;
margin-right: 10px;
font-size: 14px;
}
.etaniAnimateValue {
margin-top: 5px;
overflow: auto;
}
.etaniAVAdd {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
background-color: #a7fca7; /* 淺綠色 */
border: 1px solid #71c371; /* 細線近淺綠色 */
margin-right: 5px;
cursor: pointer;
box-sizing: border-box;
font-size: 20px; /* For plus sign SVG */
}
.etaniAVLabel {
font-size: 14px;
margin-right: 5px;
}
.etaniAV {
display: inline-block;
vertical-align: top;
}
.etaniAVItem {
display: inline-block;
width: 24px;
height: 24px;
background-color: #ff9933; /* 暗橙色 */
border: 1px dashed #00bfff; /* 虛線天藍色 */
margin: 0 5px;
box-sizing: border-box;
cursor: pointer;
}
/* ------------------ MODAL/POPUP STYLES ------------------ */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: fixed;
width: 98%;
height: 48%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1001;
box-sizing: border-box;
}
.modal-content textarea {
width: 100%;
height: calc(100% - 30px); /* Adjust based on button/header height */
resize: none;
border: 1px solid #ccc;
font-size: 12px;
box-sizing: border-box;
}
.modal-close {
position: absolute;
top: 5px;
right: 10px;
font-size: 24px;
cursor: pointer;
color: #333;
}
`;
document.head.appendChild(styleSheet);
}
// --- UTILITY FUNCTIONS ---
function svgToBase64(svgString) {
const encoder = new TextEncoder();
const svgBytes = encoder.encode(svgString);
const byteString = String.fromCharCode.apply(null, svgBytes);
const base64 = btoa(byteString);
return `data:image/svg+xml;base64,${base64}`;
}
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function updateEtaniResult() {
// ... (保持不變的 updateEtaniResult 函數) ...
if (!etaniClone) return;
const svgString = new XMLSerializer().serializeToString(etaniClone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
imgElement.src = base64Url;
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
function handleCenterClick(e) {
e.preventDefault();
if (etaniClone) {
const etdrop = etaniClone.querySelector('.etdrop');
if (etdrop) {
etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
updateEtaniResult();
}
}
}
// --- NEW HANDLERS ---
/**
* 處理「+」按鈕點擊事件 (要求六)
* 增加一個新的 animateTransform value,使用 svg#etmain 中對應 <use/> 的當前 transform 值。
*/
function handleAVAddClick(e, itemIndex, useElementId) {
e.preventDefault();
if (!etaniClone) return;
// 1. 找到 svg#etmain 中對應的 <use> 元素,獲取其 transform 值 (作為新的 value)
const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
const newTransformValue = originalUseElement ? originalUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)' : 'translate(0,0) scale(1,1) rotate(0)';
// 從 transform 字符串中提取 translate, scale, rotate 的值
const getTransformValue = (type) => {
const match = newTransformValue.match(new RegExp(`${type}\\(([^)]+)\\)`, 'i'));
return match ? match[1].split(/[,\s]+/).join(',') : (type === 'scale' ? '1,1' : '0');
};
const translateValue = getTransformValue('translate');
const scaleValue = getTransformValue('scale');
const rotateValue = getTransformValue('rotate');
// 2. 找到 etani_clone 中對應的 <use> 元素
const cloneUseElement = etaniClone.querySelector(`use[href="#${useElementId}"]`);
if (cloneUseElement) {
// 3. 更新三個 animateTransform 標籤的 values
const animates = cloneUseElement.querySelectorAll('animateTransform');
animates.forEach(animate => {
const type = animate.getAttribute('type').toLowerCase();
let currentValue = animate.getAttribute('values') || '';
let newValue = '';
if (type === 'translate') {
newValue = translateValue;
} else if (type === 'scale') {
newValue = scaleValue;
} else if (type === 'rotate') {
newValue = rotateValue;
}
// Append the new value to the existing string, separated by a semicolon
animate.setAttribute('values', (currentValue ? currentValue + ';' : '') + newValue);
});
// 4. 在 .etaniItemRight 中增加一個新的 .etaniAVItem
const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
const etaniAV = etaniItemRight.querySelector('.etaniAV');
if (etaniAV) {
const newAVItem = document.createElement('span');
newAVItem.className = 'etaniAVItem';
etaniAV.appendChild(newAVItem);
}
// 5. 即時更新 SVG
updateEtaniResult();
}
}
/**
* 處理「Transform」按鈕點擊事件 (要求四)
* 向所有 <use/> 元素添加三個 animateTransform 標籤,並在 .etaniItemRight 中增加控制項。
*/
function handleAllAppendTransformClick() {
if (!etaniClone) return;
const etdropUses = etaniClone.querySelectorAll('.etdrop use');
const etaniItemRights = document.querySelectorAll('.etaniItemRight');
etdropUses.forEach((useElement, i) => {
const useId = useElement.getAttribute('href').substring(1);
const itemRight = etaniItemRights[i];
if (!itemRight || itemRight.querySelector('.etaniAnimate')) return; // 避免重複添加
// 4.2:為 <use/> 添加 animateTransform 標籤
const baseAnimate = (type, values) => {
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
animate.setAttribute('attributeName', 'transform');
animate.setAttribute('attributeType', 'XML');
animate.setAttribute('type', type);
animate.setAttribute('values', values);
animate.setAttribute('dur', '1s');
animate.setAttribute('fill', 'freeze');
animate.setAttribute('additive', 'sum');
return animate;
};
// 初始值設置為 '0',因為 additive="sum",表示相對位移/縮放/旋轉
useElement.appendChild(baseAnimate('translate', '0,0'));
useElement.appendChild(baseAnimate('scale', '1')); // scale 初始值應為 1 (不變)
useElement.appendChild(baseAnimate('rotate', '0'));
// 4.1:為 .etaniItemRight 增加 .etaniAnimate 控制項
// 外部 wrapper
const etaniAnimate = document.createElement('div');
etaniAnimate.className = 'etaniAnimate';
// Animate Name (灰色背景白色字體)
const nameSpan = document.createElement('span');
nameSpan.className = 'etaniAnimateName';
nameSpan.textContent = 'transform';
etaniAnimate.appendChild(nameSpan);
// Animate Dur (dur: n)
const durSpan = document.createElement('span');
durSpan.className = 'etaniAnimateDur';
durSpan.textContent = 'dur: 1s'; // 初始值 1s
etaniAnimate.appendChild(durSpan);
// Animate Value container
const valueDiv = document.createElement('div');
valueDiv.className = 'etaniAnimateValue';
// AV Add (加號 SVG)
const avAddSpan = document.createElement('span');
avAddSpan.className = 'etaniAVAdd';
avAddSpan.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>`;
avAddSpan.title = 'Add Transform Value';
// 綁定事件:傳入當前的 item 索引和 use 元素的 ID
avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, i, useId));
// AV Label
const avLabelSpan = document.createElement('span');
avLabelSpan.className = 'etaniAVLabel';
avLabelSpan.textContent = 'values : ';
// AV Container
const avDiv = document.createElement('div');
avDiv.className = 'etaniAV';
// AV Item (最初只含一個)
const avItemSpan = document.createElement('span');
avItemSpan.className = 'etaniAVItem';
avDiv.appendChild(avItemSpan);
valueDiv.appendChild(avAddSpan);
valueDiv.appendChild(avLabelSpan);
valueDiv.appendChild(avDiv);
etaniAnimate.appendChild(valueDiv);
itemRight.appendChild(etaniAnimate);
});
// 即時更新 SVG
updateEtaniResult();
}
/**
* 處理 Setting Mode (Freeze/Repeat) 切換 (要求五)
*/
function handleSettingModeChange(mode) {
if (!etaniClone) return;
const animates = etaniClone.querySelectorAll('animateTransform');
const isRepeat = mode === 'repeat';
animates.forEach(animate => {
if (isRepeat) {
animate.removeAttribute('fill');
animate.setAttribute('repeatCount', 'indefinite');
} else {
animate.removeAttribute('repeatCount');
animate.setAttribute('fill', 'freeze');
}
});
// 更新 active 狀態
document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);
updateEtaniResult();
}
/**
* 處理 HTML 彈出視窗 (要求七)
*/
function handleContentHTMLClick(e) {
e.preventDefault();
if (!etaniClone) return;
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
const content = document.createElement('div');
content.className = 'modal-content';
const close = document.createElement('span');
close.className = 'modal-close';
close.innerHTML = '×'; // 'x' icon
const textarea = document.createElement('textarea');
textarea.value = etaniClone.outerHTML; // 輸出 etani_clone 的 outerHTML
// 關閉邏輯
const closeModal = () => {
document.body.removeChild(overlay);
document.body.removeChild(content);
};
close.onclick = closeModal;
overlay.onclick = closeModal; // 點擊背景也關閉
content.appendChild(close);
content.appendChild(textarea);
document.body.appendChild(overlay);
document.body.appendChild(content);
}
// --- MAIN STRUCTURE CREATION ---
function createEtaniInner(etaniouter) {
// 5.1, 5.2, 5.3 Clone, remove .etwait, update ID
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etaniClone = originalSvg.cloneNode(true);
const etwaitElement = etaniClone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
etaniClone.id = 'etmainani';
// Create div.etaniinner
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
// Create div.etaniCtrl
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
// 要求七:a.etaniContentHTML
const etaniContent = document.createElement('div');
etaniContent.className = 'etaniContent';
const contentHTMLLink = document.createElement('a');
contentHTMLLink.className = 'etaniContentHTML';
contentHTMLLink.textContent = 'Show HTML';
contentHTMLLink.href = '#';
contentHTMLLink.addEventListener('click', handleContentHTMLClick);
etaniContent.appendChild(contentHTMLLink);
// a.etaniCenter
const centerLink = document.createElement('a');
centerLink.className = 'etaniCenter';
centerLink.textContent = 'Center';
centerLink.href = '#';
centerLink.addEventListener('click', handleCenterClick);
etaniContent.appendChild(centerLink);
etaniCtrl.appendChild(etaniContent);
// 要求一、二:div.etaniSetting (Radio Buttons)
const etaniSetting = document.createElement('div');
etaniSetting.className = 'etaniSetting';
const freezeRadio = document.createElement('span');
freezeRadio.className = 'etaniSettingMode etaniSettingFreeze active';
freezeRadio.textContent = 'Freeze';
freezeRadio.setAttribute('data-mode', 'freeze');
freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));
const repeatRadio = document.createElement('span');
repeatRadio.className = 'etaniSettingMode etaniSettingRepeat';
repeatRadio.textContent = 'Repeat';
repeatRadio.setAttribute('data-mode', 'repeat');
repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
etaniSetting.appendChild(freezeRadio);
etaniSetting.appendChild(repeatRadio);
etaniCtrl.appendChild(etaniSetting);
// 要求一、三:div.etaniAllAppend (Buttons)
const etaniAllAppend = document.createElement('div');
etaniAllAppend.className = 'etaniAllAppend';
const transformButton = document.createElement('button');
transformButton.className = 'etaniAllAppendTransform';
transformButton.textContent = 'transform';
transformButton.addEventListener('click', handleAllAppendTransformClick);
const opacityButton = document.createElement('button');
opacityButton.className = 'etaniAllAppendOpacity';
opacityButton.textContent = 'opacity';
// opacityButton.addEventListener('click', handleAllAppendOpacityClick); // Placeholder
etaniAllAppend.appendChild(transformButton);
etaniAllAppend.appendChild(opacityButton);
etaniCtrl.appendChild(etaniAllAppend);
// Create div.etaniCol (Tile list)
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
// Create div.etaniResult
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
// Result elements (img, download, rename, size) ... (略,與前次相同)
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = '#';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = '#';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv);
etaniResult.appendChild(sizeSpan);
// Append children to etaniinner
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// III, IV, V, VI, VII. Populate etaniCol
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
// (Tile processing logic - 保持不變)
const tileid = useElement.getAttribute('href').substring(1);
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
if (targetUse) {
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
const originalTile = document.querySelector(`defs g#${tileid}`);
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
etaniCol.appendChild(etaniItem);
});
// 5. Update the result section immediately
updateEtaniResult();
}
// --- INITIALIZATION ---
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner');
if (etanibutton.textContent === 'Animate it') {
etanibutton.textContent = 'Close Ejtile Ani';
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
etanibutton.textContent = 'Animate it';
if (etaniinner) {
etaniinner.remove();
}
etaniClone = null;
}
}
window.addEventListener('load', () => {
addDynamicStyles();
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
etanibutton.addEventListener('click', toggleAnimation);
etaniouter.appendChild(etanibutton);
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣