Reputation: 1147
Is it possible to use javascript to control which frame of a GIF
image is showing and/or stop the animation. I want to be able to load a GIF
with several frames and only show one of them.
I know I could do it with lots of separate images, but if I can do what I want with a GIF
, it will only be one file to worry about.
Upvotes: 88
Views: 147267
Reputation: 7334
Update 2024
This question was posted in 2010 and is in need of an update. The previous answers use techniques and libraries that are rarely used anymore. Yet, it continues to be cited as a solution.
Browsers now support the video element. These will not play animated GIF images directly. Yet, it's very easy to convert GIF images to MP4 format using graphics applications or one of the many online converters. The WEBM format is a widely supported alternative when a transparent background is required.
Video has many advantages over an animated GIF. A few of these are:
Code Snippet
The snippet plays an MP4, WEBM, and GIF side by side. The MP4 and WEBM use an HTML-5 video element. The GIF uses the library from the most upvoted answer. There are buttons for play, pause, and seek.
const toggle = document.querySelector('input[type=checkbox]');
const mp4 = document.querySelector('#mp4 video');
const webm = document.querySelector('#webm video');
const gif = new SuperGif({
gif: document.querySelector('#gif img'),
loop_mode: true,
auto_play: true,
max_width: 150,
// on_end:.
// loop_delay:
show_progress_bar: false
});
gif.load();
play.addEventListener('click', () => {
mp4.play();
webm.play();
gif.play();
})
pause.addEventListener('click', () => {
mp4.pause();
webm.pause();
gif.pause();
})
seek.addEventListener('change', () => {
let percent = seek.value / 100;
let frame = Math.floor(gif.get_length() * percent)
gif.move_to(frame);
let position = Math.floor(mp4.duration * percent);
mp4.currentTime = position;
position = Math.floor(webm.duration * percent);
webm.currentTime = position;
})
mp4.addEventListener('timeupdate', () => {
seek.value = Math.floor(100 * mp4.currentTime / mp4.duration);
});
body {
font-family: sans-serif;
.container {
margin-top: 1rem;
display: flex;
background-image: linear-gradient(to bottom right, red, yellow);
}
input[type=button] {
min-width: 4rem;
}
input[type=range] {
min-width: 20rem;
}
figure {
border: thin solid white;
border-radius: 0.5rem;
display: inline-block;
padding: 0.25rem;
margin: 0.5rem;
background-color: transparent;
}
figcaption {
color: white;
text-align: center;
}
video,
img {
width: 150px;
height: auto;
}
<input type="button" id="play" value="Play">
<input type="button" id="pause" value="Pause">
<input type="range" id="seek" min="0" max="100" value="0">
<div class="container">
<figure id="mp4">
<figcaption>
205kb Video MP4
</figcaption>
<video autoplay muted loop>
<source src="https://i.yourimageshare.com/i0rmDubApf.mp4">
</video>
</figure>
<figure id="webm">
<figcaption>
222kb Video WEBM
</figcaption>
<video autoplay muted loop>
<source src="https://i.yourimageshare.com/S1S9fJww3M.webm">
</video>
</figure>
<figure id="gif">
<figcaption>
746kb Animated GIF
</figcaption>
<image src="https://i.postimg.cc/t4zZRyZH/rubik.gif" />
</figure>
</div>
<script src="https://cdn.jsdelivr.net/gh/buzzfeed/libgif-js/libgif.js"></script>
Upvotes: 10
Reputation: 25838
If you are OK with converting your gif to a sprite sheet, you can do it this way (using ImageMagick):
montage animation.gif -coalesce -tile x1 -geometry +0+0 -background None -quality 100 spritesheet.png
It is even likely that the new image will be of lesser size.
Once you have a sprite sheet, use CSS animation. An animation with a fixed frame time is used here:
var el = document.getElementById('anim');
function play() {
el.style.animationPlayState = 'running';
}
function pause() {
el.style.animationPlayState = 'paused';
}
function reset() {
el.style.animation = 'none';
el.offsetHeight; /* trigger reflow to apply the change immediately */
el.style.animation = null;
}
function stop() {
reset();
pause();
}
#anim {
background-image: url('https://i.sstatic.net/J5drY.png');
width: 250px;
height: 188px;
animation: anim 1.0s steps(10) infinite;
}
@keyframes anim {
100% { background-position: -2500px; }
}
<div id="anim" title="Animated Bat by Calciumtrice"></div>
<button onclick="play()">Play</button>
<button onclick="pause()">Pause</button>
<button onclick="reset()">Reset</button>
<button onclick="stop()">Stop</button>
If you want the animation to not start automatically, add paused
to the end of animation
rule.
Upvotes: 38
Reputation: 1263
You can use the libgif library.
It allows you to start/stop the gif and control which frame the gif is on.
<script type="text/javascript" src="./libgif.js"></script>
<img src="./example1_preview.gif" rel:animated_src="./example1.gif"
width="360" height="360" rel:auto_play="1" rel:rubbable="1" />
<script type="text/javascript">
$$('img').each(function (img_tag) {
if (/.*\.gif/.test(img_tag.src)) {
var rub = new SuperGif({ gif: img_tag } );
rub.load(function(){
console.log('oh hey, now the gif is loaded');
});
}
});
</script>
(most of the code is taken directly from their example)
Upvotes: 71
Reputation: 1154
I use x-gif it's pretty cool and easy to setup.
From Github:
<x-gif src="probably_cats.gif"></x-gif>
Where you can add the following as attributes:
speed="1.0"
(default mode) multiplies the speed by the value of the attribute;sync
defers playback to an external object;bpm="120"
syncs GIFs to a given beats-per-minute;Options:
stopped
prevents the GIF from animating;
fill
causes the GIF to expand to cover its container;
n-times="3.0"
(speed mode only) stops playback (by adding the attribute stopped) after a set number of times;snap
(sync & bpm modes only) instead of allowing longer GIFs to sync to multiple beats, force them to fit into only one;ping-pong
plays the GIF front-to-back then back-to-front;debug
turns on debug output from the Gif Exploder;exploded
stops playback, and renders each frame out side-by-side.Upvotes: 35