Skip to content

Commit 4ebf55d

Browse files
committed
Add Animation in image
1 parent f6b08f6 commit 4ebf55d

3 files changed

Lines changed: 143 additions & 5 deletions

File tree

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<input type="file" accept="image/*" id="image-input" hidden>
2727
</label>
2828
<button class="btn" id="reset-btn"><i class="ri-refresh-line"></i> Reset</button>
29+
<button class="btn" id="anim-btn"><i class="ri-movie-line"></i> Animate</button>
30+
<button class="btn" id="record-btn" disabled><i class="ri-record-circle-line"></i> Record</button>
2931
<button class="btn" id="download-btn"><i class="ri-download-line"></i> Download</button>
3032
</div>
3133

script.js

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,19 @@ const imageCanvas = document.querySelector('#image-canvas');
1818
const imgInput = document.querySelector('#image-input');
1919
const resetButton = document.querySelector('#reset-btn');
2020
const downloadButton = document.querySelector('#download-btn');
21+
const animButton = document.querySelector('#anim-btn');
22+
const recordButton = document.querySelector('#record-btn');
2123
const dropZone = document.querySelector('.bottom');
2224
const canvasCtx = imageCanvas.getContext('2d');
2325
let file = null;
2426
let image = null;
2527

28+
let isAnimating = false;
29+
let animationId = null;
30+
let mediaRecorder = null;
31+
let recordedChunks = [];
32+
let isRecording = false;
33+
2634
function createFilterElement(name, unit = '%', value, min, max) {
2735
const div = document.createElement('div');
2836
div.classList.add('filter');
@@ -52,7 +60,7 @@ function createFilterElement(name, unit = '%', value, min, max) {
5260
input.addEventListener('input', (event) => {
5361
filters[name].value = input.value;
5462
valDisplay.innerText = `${input.value}${unit}`;
55-
applyFilters();
63+
if (!isAnimating) applyFilters();
5664
});
5765

5866
return div;
@@ -124,6 +132,7 @@ function loadImage(file) {
124132
imageCanvas.width = img.width;
125133
imageCanvas.height = img.height;
126134
applyFilters();
135+
if (recordButton) recordButton.disabled = false;
127136
};
128137
}
129138

@@ -132,10 +141,8 @@ imgInput.addEventListener('change', () => {
132141
if (file) loadImage(file);
133142
});
134143

135-
function applyFilters() {
136-
if (!image) return;
137-
canvasCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
138-
canvasCtx.filter = `
144+
function getFilterString() {
145+
return `
139146
brightness(${filters.brightness.value}${filters.brightness.unit})
140147
contrast(${filters.contrast.value}${filters.contrast.unit})
141148
saturate(${filters.saturation.value}${filters.saturation.unit})
@@ -146,11 +153,123 @@ function applyFilters() {
146153
opacity(${filters.opacity.value}${filters.opacity.unit})
147154
invert(${filters.invert.value}${filters.invert.unit})
148155
`.trim();
156+
}
157+
158+
function applyFilters() {
159+
if (!image) return;
160+
canvasCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
161+
canvasCtx.filter = getFilterString();
149162
canvasCtx.drawImage(image, 0, 0);
150163
}
151164

165+
// --- Animation Logic ---
166+
167+
function toggleAnimation() {
168+
if (isAnimating) {
169+
isAnimating = false;
170+
cancelAnimationFrame(animationId);
171+
animButton.innerHTML = `<i class="ri-movie-line"></i> Animate`;
172+
applyFilters(); // Reset to static
173+
} else {
174+
if (!image) return;
175+
isAnimating = true;
176+
animButton.innerHTML = `<i class="ri-pause-line"></i> Stop`;
177+
animateGlitch();
178+
}
179+
}
180+
181+
animButton.addEventListener('click', toggleAnimation);
182+
183+
function animateGlitch() {
184+
if (!isAnimating) return;
185+
186+
// 1. Draw base filtered image
187+
canvasCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
188+
canvasCtx.filter = getFilterString();
189+
canvasCtx.drawImage(image, 0, 0);
190+
191+
// 2. Add Glitch Effects
192+
if (Math.random() > 0.8) {
193+
const sliceHeight = Math.random() * 50 + 5;
194+
const sliceY = Math.random() * imageCanvas.height;
195+
const offset = (Math.random() - 0.5) * 40;
196+
canvasCtx.drawImage(
197+
imageCanvas,
198+
0, sliceY, imageCanvas.width, sliceHeight, // Source
199+
offset, sliceY, imageCanvas.width, sliceHeight // Dest
200+
);
201+
}
202+
203+
// RGB Split / Color Shift
204+
if (Math.random() > 0.9) {
205+
const offset = (Math.random() - 0.5) * 10;
206+
canvasCtx.globalCompositeOperation = 'screen';
207+
canvasCtx.filter = getFilterString() + ` hue-rotate(90deg) opacity(0.5)`;
208+
canvasCtx.drawImage(image, offset, 0);
209+
canvasCtx.globalCompositeOperation = 'source-over';
210+
}
211+
212+
animationId = requestAnimationFrame(animateGlitch);
213+
}
214+
215+
// --- Recording Logic ---
216+
217+
recordButton.addEventListener('click', () => {
218+
if (isRecording) {
219+
stopRecording();
220+
} else {
221+
startRecording();
222+
}
223+
});
224+
225+
function startRecording() {
226+
if (!image) return;
227+
228+
// Force animation if not active
229+
if (!isAnimating) toggleAnimation();
230+
231+
const stream = imageCanvas.captureStream(30);
232+
mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' });
233+
234+
recordedChunks = [];
235+
isRecording = true;
236+
recordButton.innerHTML = `<i class="ri-stop-circle-line"></i> Stop Rec`;
237+
recordButton.classList.add('recording-active');
238+
239+
mediaRecorder.ondataavailable = (e) => {
240+
if (e.data.size > 0) recordedChunks.push(e.data);
241+
};
242+
243+
mediaRecorder.onstop = exportVideo;
244+
mediaRecorder.start();
245+
}
246+
247+
function stopRecording() {
248+
isRecording = false;
249+
recordButton.innerHTML = `<i class="ri-record-circle-line"></i> Record`;
250+
recordButton.classList.remove('recording-active');
251+
mediaRecorder.stop();
252+
if (isAnimating) toggleAnimation();
253+
}
254+
255+
function exportVideo() {
256+
const blob = new Blob(recordedChunks, { type: 'video/webm' });
257+
const url = URL.createObjectURL(blob);
258+
const a = document.createElement('a');
259+
a.style.display = 'none';
260+
a.href = url;
261+
a.download = `glitch-animation-${Date.now()}.webm`;
262+
document.body.appendChild(a);
263+
a.click();
264+
setTimeout(() => {
265+
document.body.removeChild(a);
266+
window.URL.revokeObjectURL(url);
267+
}, 100);
268+
}
269+
152270
resetButton.addEventListener('click', () => {
153271
filters = JSON.parse(JSON.stringify(defaultFilters));
272+
if (isAnimating) toggleAnimation();
154273
applyFilters();
155274
createFilters();
156275
});

style.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,23 @@ main section .bottom .placeholder i {
144144
opacity: 0.5;
145145
}
146146

147+
#record-btn.recording-active {
148+
background-color: var(--bg-danger-color);
149+
color: white;
150+
box-shadow: 0 0 10px red;
151+
animation: pulse 1s infinite alternate;
152+
}
153+
154+
@keyframes pulse {
155+
from {
156+
opacity: 1;
157+
}
158+
159+
to {
160+
opacity: 0.7;
161+
}
162+
}
163+
147164
canvas {
148165
max-width: 100%;
149166
max-height: 100%;

0 commit comments

Comments
 (0)