Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions 14-css-animation-builder/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CSS Animation Builder</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<main class="container">
<h1>CSS Animation Builder</h1>

<section class="preview-section">
<div class="preview-stage">
<div id="preview-box" class="box"></div>
</div>
</section>

<section class="controls-section">
<!-- Transformaciones -->
<div class="group">
<h2>Transforms</h2>

<label>Translate X (px)
<input type="range" id="tx" min="-200" max="200" value="0" />
</label>

<label>Translate Y (px)
<input type="range" id="ty" min="-200" max="200" value="0" />
</label>

<label>Scale
<input type="range" id="scale" min="0.1" max="2" step="0.1" value="1" />
</label>

<label>Rotate (deg)
<input type="range" id="rotate" min="-180" max="180" value="0" />
</label>

<label>Opacity
<input type="range" id="opacity" min="0" max="1" step="0.1" value="1" />
</label>
</div>

<!-- Propiedades de animación -->
<div class="group">
<h2>Animation</h2>

<label>Duration (s)
<input type="range" id="duration" min="0.1" max="5" step="0.1" value="1" />
</label>

<label>Delay (s)
<input type="range" id="delay" min="0" max="3" step="0.1" value="0" />
</label>

<label>Easing
<select id="easing">
<option value="ease">ease</option>
<option value="linear">linear</option>
<option value="ease-in">ease-in</option>
<option value="ease-out">ease-out</option>
<option value="ease-in-out">ease-in-out</option>
</select>
</label>

<label>Iterations
<input type="number" id="iterations" min="1" value="1" />
</label>

<label>Direction
<select id="direction">
<option value="normal">normal</option>
<option value="alternate">alternate</option>
</select>
</label>
</div>
</section>

<section class="output-section">
<h2>Generated CSS</h2>
<textarea id="output" readonly></textarea>
<button id="copy-btn">Copy</button>
</section>
</main>

<script src="./script.js"></script>
</body>
</html>
162 changes: 162 additions & 0 deletions 14-css-animation-builder/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@


const box = document.getElementById('preview-box');
const output = document.getElementById('output');
const copyBtn = document.getElementById('copy-btn');

const controls = {
tx: document.getElementById('tx'),
ty: document.getElementById('ty'),
scale: document.getElementById('scale'),
rotate: document.getElementById('rotate'),
opacity: document.getElementById('opacity'),
duration: document.getElementById('duration'),
delay: document.getElementById('delay'),
easing: document.getElementById('easing'),
iterations: document.getElementById('iterations'),
direction: document.getElementById('direction')
};

function getSettings() {
const tx = Number(controls.tx.value) || 0;
const ty = Number(controls.ty.value) || 0;
const scale = Number(controls.scale.value) || 1;
const rotate = Number(controls.rotate.value) || 0;
const opacity = Number(controls.opacity.value);
const duration = Number(controls.duration.value) || 1;
const delay = Number(controls.delay.value) || 0;
const easing = controls.easing.value || 'ease';
const iterationsRaw = Number(controls.iterations.value);
const iterations = iterationsRaw > 0 ? iterationsRaw : 1;
const direction = controls.direction.value || 'normal';

return {
tx,
ty,
scale,
rotate,
opacity,
duration,
delay,
easing,
iterations,
direction
};
}

function ensureStyleTag() {
let styleTag = document.getElementById('dynamic-keyframes');
if (!styleTag) {
styleTag = document.createElement('style');
styleTag.id = 'dynamic-keyframes';
document.head.appendChild(styleTag);
}
return styleTag;
}

function buildCSS(settings) {
const {
tx,
ty,
scale,
rotate,
opacity,
duration,
delay,
easing,
iterations,
direction
} = settings;

const fromTransform = 'translate(0px, 0px) scale(1) rotate(0deg)';
const toTransform = `translate(${tx}px, ${ty}px) scale(${scale}) rotate(${rotate}deg)`;
const fromOpacity = 1;
const toOpacity = opacity;

const animationLine = `animation: myAnim ${duration}s ${easing} ${delay}s ${iterations} ${direction};`;

const keyframes = `
@keyframes myAnim {
from {
transform: ${fromTransform};
opacity: ${fromOpacity};
}
to {
transform: ${toTransform};
opacity: ${toOpacity};
}
}
`.trim();

const css = `.my-element {
${animationLine}
}

${keyframes}
`;

return { css, keyframes, animationLine };
}

function applyAnimation() {
const settings = getSettings();
const { css, keyframes, animationLine } = buildCSS(settings);

// Restart animation
box.style.animation = 'none';
// Force reflow so the browser picks up the reset
void box.offsetWidth;

box.style.animation = `myAnim ${settings.duration}s ${settings.easing} ${settings.delay}s ${settings.iterations} ${settings.direction}`;

const styleTag = ensureStyleTag();
styleTag.textContent = keyframes;

output.value = css;
}

function attachListeners() {
Object.values(controls).forEach((el) => {
const eventName = el.tagName === 'SELECT' ? 'change' : 'input';
el.addEventListener(eventName, applyAnimation);
});

copyBtn.addEventListener('click', () => {
const text = output.value.trim();
if (!text) return;

if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => flashCopyState(), () => fallbackCopy(text));
} else {
fallbackCopy(text);
}
});
}

function fallbackCopy(text) {
output.focus();
output.select();
try {
document.execCommand('copy');
flashCopyState();
} catch (err) {
// ignore
} finally {
output.setSelectionRange(0, 0);
output.blur();
}
}

function flashCopyState() {
const original = copyBtn.textContent;
copyBtn.textContent = 'copied';
copyBtn.disabled = true;

setTimeout(() => {
copyBtn.textContent = original;
copyBtn.disabled = false;
}, 1200);
}

attachListeners();
applyAnimation();
Loading