画像をアニメーションGIFに変換
ドラッグ&ドロップで追加された画像からアニメーションgifを生成
Canvas Image Animation Gif
ぴょんぴょんはねたり、回転したり、近づいたり、スライド移動する動画が作成できます
view source
JavaScript
// 処理手順
// 1. HTML5 ドラッグ&ドロップで画像取得
// 2. canvasに埋め込む
// 3. canvasを変形
// 4. png生成
// 5. 3,4をフレーム風に繰り返し
// 6. pngを集めてアニメーションgifに変換
// HTML5 ドラッグ&ドロップで取得
(function(){
'use strict';
var inputFile = document.getElementById('file');
var dragArea = document.getElementById('dragArea');
var fileName = 'ani.gif';
var background = '#FFFFFF';
var ele_background = document.getElementById('background');
ele_background.addEventListener('change', function(ev){
background = ev.target.value;
});
// animetionPattern
// scaleX, scaleY, dpX, dpY,
var frames = {
bound: [
[1.6 ,2],
[1 , 1, 0, 0],
[1.1, .9, 0, .2],
[1.2, .8, 0, .4],
[1.6, .4, 0, .9],
[1.2, .8, 0, .7],
[1 , 1 , 0, .5],
[ .4, 1.6 ,0, 0],
[ .8, 1.2, 0, -.4],
[1.2, .8, 0, -.6],
[1.1, .9, 0, -.4],
],
zoom: [
[2 ,2],
[1 , 1 , 0, 0],
[1.1, 1.1, 0, 0],
[1.2, 1.2, 0, 0],
[1.6, 1.6, 0, 0],
[2 , 2 , 0, 0],
[3 , 3 , 0, 0],
[4 , 4 , 0, 0],
[8 , 8 , 0, 0],
[ .1, .1, 0, 0],
[ .2, .2, 0, 0],
[ .5, .5, 0, 0],
],
beat: [
[1.1 ,1.1],
[1 , 1 , 0, 0],
[1.05, 1.05, 0, 0],
[1.1 , 1.1 , 0, 0],
[1.05, 1.05, 0, 0],
[1 , 1, 0, 0],
[.95 , .95, 0, 0],
[.9 , .9 , 0, 0],
[.95 , .95, 0, 0],
],
momimomi: [
[1.2 ,1.2],
[1 , 1 , 0, 0],
[0.9 , 1.1, 0, 0],
[0.8 , 1.2, 0, 0],
[0.9 , 1.1, 0, 0],
[1 , 1 , 0, 0],
[1.1 , .9, 0, 0],
[1.2 , .8, 0, 0],
[1.1 , .9, 0, 0],
],
marqueeX1: [
[2 ,1],
[1 , 1, 2, 0],
[1 , 1, 1.6, 0],
[1 , 1, 1, 0],
[1 , 1, .9, 0],
[1 , 1, .8, 0],
[1 , 1, .8, 0],
[1 , 1, .7, 0],
[1 , 1, .6, 0],
[1 , 1, .5, 0],
[1 , 1, .4, 0],
[1 , 1, .3, 0],
[1 , 1, .2, 0],
[1 , 1, .1, 0],
[1 , 1, 0, 0],
[1 , 1, -.1, 0],
[1 , 1, -.2, 0],
[1 , 1, -.3, 0],
[1 , 1, -.4, 0],
[1 , 1, -.5, 0],
[1 , 1, -.6, 0],
[1 , 1, -.7, 0],
[1 , 1, -.8, 0],
[1 , 1, -.9, 0],
[1 , 1, -1, 0],
[1 , 1, -1.2, 0],
[1 , 1, -1.4, 0],
[1 , 1, -1.6, 0],
[1 , 1, -1.8, 0],
[1 , 1, -2, 0],
],
marqueeX2: [
[2 ,1],
[1 , 1, -3, 0],
[1 , 1, -2, 0],
[1 , 1, -1.8, 0],
[1 , 1, -1.6, 0],
[1 , 1, -1.4, 0],
[1 , 1, -1.2, 0],
[1 , 1, -1, 0],
[1 , 1, -.9, 0],
[1 , 1, -.8, 0],
[1 , 1, -.7, 0],
[1 , 1, -.6, 0],
[1 , 1, -.5, 0],
[1 , 1, -.4, 0],
[1 , 1, -.3, 0],
[1 , 1, -.2, 0],
[1 , 1, -.1, 0],
[1 , 1, 0, 0],
[1 , 1, .1, 0],
[1 , 1, .2, 0],
[1 , 1, .3, 0],
[1 , 1, .4, 0],
[1 , 1, .5, 0],
[1 , 1, .6, 0],
[1 , 1, .7, 0],
[1 , 1, .8, 0],
[1 , 1, .8, 0],
[1 , 1, .9, 0],
[1 , 1, 1, 0],
[1 , 1, 1.2, 0],
[1 , 1, 1.4, 0],
[1 , 1, 1.6, 0],
[1 , 1, 1.8, 0],
[1 , 1, 2, 0],
[1 , 1, 3, 0],
],
slideX: [
[2 ,1],
[1 , 1, 0, 0],
[1 , 1,-.1, 0],
[1 , 1,-.2, 0],
[1 , 1,-.4, 0],
[1 , 1,-.2, 0],
[1 , 1, 0, 0],
[1 , 1, .1, 0],
[1 , 1, .2, 0],
[1 , 1, .4, 0],
[1 , 1, .2, 0],
],
moveX: [
[2 ,1],
[-1 , 1, 0, 0],
[-1 , 1,-.1, 0],
[-1 , 1,-.2, 0],
[1 , 1,-.4, 0],
[1 , 1,-.2, 0],
[1 , 1, 0, 0],
[1 , 1, .1, 0],
[1 , 1, .2, 0],
[-1 , 1, .4, 0],
[-1 , 1, .2, 0],
],
kyoroX: [
[2 ,1],
[1 , 1, 0, 0],
[-1 , 1, 0, 0],
],
kasakasaX: [
[2 ,1],
[1 , 1, -1, 0],
[1 , 1, -.7, 0],
[1 , 1, -.8, 0],
[1 , 1, -.6, 0],
[1 , 1, -.5, 0],
[1 , 1, -.3, 0],
[1 , 1, -.4, 0],
[1 , 1, -.1, 0],
[1 , 1, -.2, 0],
[1 , 1, .1, 0],
[1 , 1, 0, 0],
[1 , 1, .3, 0],
[1 , 1, .2, 0],
[1 , 1, .5, 0],
[1 , 1, .4, 0],
[1 , 1, .7, 0],
[1 , 1, .6, 0],
[1 , 1, .9, 0],
[1 , 1, .8, 0],
[1 , 1, 1, 0],
[1 , 1, 2, 0],
],
rocketY: [
[1, 2],
[1 , 1, 0, 0],
[1 , 1, 0, -.1],
[1 , 1, 0, -.2],
[1 , 1, 0, -.4],
[1 , 1, 0, -.8],
[1 , 1, 0, -1.2],
[1 , 1, 0, .9],
[1 , 1, 0, .6],
[1 , 1, 0, .3],
[1 , 1, 0, .1],
],
fallY: [
[1, 2],
[1 , 1, 0, -2],
[1 , 1, 0, -1.8],
[1 , 1, 0, -1.6],
[1 , 1, 0, -1.4],
[1 , 1, 0, -1.2],
[1 , 1, 0, -1],
[1 , 1, 0, -.8],
[1 , 1, 0, -.4],
[1 , 1, 0, 0],
[1 , 1, 0, .8],
[1 , 1, 0, 1.6],
[1 , 1, 0, 2],
[1 , 1, 0, 3],
],
fallXY: [
[2, 2],
[1 , 1, 2, -2],
[1 , 1, 1.5, -1.5],
[1 , 1, 1, -1],
[1 , 1, .5, -.5],
[1 , 1, .3, -.3],
[1 , 1, .2, -.2],
[1 , 1, .0, -.0],
[1 , 1, -.1, .1],
[1 , 1, -.2, .2],
[1 , 1, -.5, .5],
[1 , 1, -.6, .6],
[1 , 1, -.7, .7],
[1 , 1, -.8, .8],
[1 , 1, -.9, .9],
[1 , 1, -1.0, 1.0],
[1 , 1, -1.2, 1.2],
[1 , 1, -1.4, 1.4],
[1 , 1, -1.6, 1.6],
[1 , 1, -2, 2],
],
tremble: [
[1 ,1],
[1 , 1, 0, 0],
[1 , 1, .05, 0],
[1 , 1, -.05, 0],
[1 , 1, .05, 0],
[1 , 1, -.05, 0],
[1 , 1, .05, 0],
[1 , 1, -.05, 0],
[1 , 1, .05, 0],
[1 , 1, -.05, 0],
],
circle: [
[2 ,2],
[1 , 1, 0, .5],
[1 , 1, .35, .35],
[1 , 1, .5, 0],
[1 , 1, .35, -.35],
[1 , 1, 0, -.5],
[1 , 1, -.35, -.35],
[1 , 1, -.5, 0],
[1 , 1, -.35, .35],
],
rotate: [
[1 ,1],
[1 , 1, 0, 0, 0],
[1 , 1, 0, 0, 30],
[1 , 1, 0, 0, 60],
[1 , 1, 0, 0, 90],
[1 , 1, 0, 0, 120],
[1 , 1, 0, 0, 150],
[1 , 1, 0, 0, 180],
[1 , 1, 0, 0, 210],
[1 , 1, 0, 0, 240],
[1 , 1, 0, 0, 270],
[1 , 1, 0, 0, 300],
[1 , 1, 0, 0, 330],
],
rotateAccel: [
[1 ,1],
[1 , 1, 0, 0, 0],
[1 , 1, 0, 0, 15],
[1 , 1, 0, 0, 45],
[1 , 1, 0, 0, 90],
[1 , 1, 0, 0, 150],
[1 , 1, 0, 0, 225],
[1 , 1, 0, 0, 315],
],
rotate90: [ //模様に使える
[1 ,1],
[1 , 1, 0, 0, 0],
[1 , 1, 0, 0, 90],
],
blink: [
[1 ,1],
[1 , 1, 0, 0],
[1 , 1, 3, 0],
],
round: [
[2.4 ,2.4],
[1 , 1, .3, -.5],
[1 , 1, .5, -.3],
[ .8,1.5, .8, .0],
[1 , 1, .5, .3],
[1 , 1, .3, .5],
[1.5, .8, .0, .8],
[1 , 1, -.3, .5],
[1 , 1, -.5, .3],
[ .8,1.5, -.8, .0],
[1 , 1, -.5, -.3],
[1 , 1, -.3, -.5],
[1.5, .8, .0, -.8],
],
boundRotate: [
[2 ,2],
[1 , 1, 0, 0, 0],
[1.1, 0.9, 0, .2, 45],
[1.2, 0.8, 0, .4, 90],
[1.6, 0.4, 0, .9, 135],
[1.2, 0.8, 0, .7, 180],
[1 , 1 , 0, .5, 225],
[0.4, 1.6 ,0, 0, 270],
[0.8, 1.2, 0, -.4, 300],
[1.2, 0.8, 0, -.6, 310],
[1.1, 0.9, 0, -.4, 330],
],
};
// アニメパターン選択肢
var labels =[];
var animetionPattern = 'bound';
var inputArea = document.getElementById('inputArea');
for(var key in frames){
var val = key;
var label = document.createElement('label');
var input = document.createElement('input');
var text = document.createTextNode(val);
labels.push(label);
input.setAttribute('type', 'radio');
input.setAttribute('value', val);
input.setAttribute('name', 'animetionPattern');
inputArea.appendChild(label);
label.appendChild(input);
label.appendChild(text);
input.addEventListener('change', function(ev){
for(var i=0,len=labels.length;i<len;i++){
labels[i].classList.remove('selected');
};
ev.target.parentElement.classList.add('selected');
});
if(val === animetionPattern){
input.checked = true;
label.classList.add('selected');
}
}
var labels = document.getElementsByTagName('label');
for(var i=0,len=labels.length;i<len;i++){
labels[i].addEventLister
}
// ファイルアップロード
dragArea.classList.add('ready');
dragArea.addEventListener('dragstart', function(event){
event.dataTranfer.addElement(container);
});
dragArea.addEventListener('dragover', function(event){
event.preventDefault();
this.classList.add('drag');
});
dragArea.addEventListener('dragleave', function(event){
event.preventDefault();
this.classList.remove('drag');
});
dragArea.addEventListener('drop', function(event){
event.preventDefault();
this.classList.remove('drag');
console.log(event.dataTransfer.files);
if(!event.dataTransfer.files.length){
return;
}
var file = event.dataTransfer.files[0];
fileName = file.name.substring(0,12).split('.')[0] || 'name';
console.log(file);
var reader = new FileReader();
reader.readAsDataURL(file)
reader.addEventListener('load', function(){
var dataURL = reader.result;
console.log(dataURL);
var newImage = new Image();
newImage.src = dataURL;
newImage.onload = function(){
insertCanvas(dataURL, this);
}
});
});
//ドラッグでなくフォームで受け取る場合
//http://www.html5rocks.com/ja/tutorials/file/dndfiles/
inputFile.addEventListener('change', function(event){
var file = event.target.files[0];
if (!file.type.match('image.*')){
return;
}
console.log(file);
var reader = new FileReader();
reader.onload = function(){
var dataURL = reader.result;
console.log(dataURL);
var newImage = new Image();
newImage.src = dataURL;
newImage.onload = function(){
insertCanvas(dataURL, this);
}
}
reader.readAsDataURL(file);
});
//canvas内でアニメーション毎にpng生成
var insertCanvas = function(dataURL, newImage){
console.log(newImage.naturalWidth);
var dataURLDefault = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABZUlEQVR4Xu3TQREAAAiEQK9/aWvsAxMw4O06ysAommCuINgTFKQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LQQL8oTPAGUY76lBAAAAAElFTkSuQmCC';
var image = new Image();
var x,y;
var dX=0,dY=0;
var scaleX=1,scaleY=1;
var canvas = document.getElementById('canvas');
var ctx;
var pngArea = document.getElementById('pngArea');
ctx = canvas.getContext('2d');
var radioList = document.getElementsByName('animetionPattern');
for(var i=0,len=radioList.length; i<len; i++){
if (radioList[i].checked) {
animetionPattern = radioList[i].value;
break;
}
}
var scaleCanvas = frames[animetionPattern].shift() || [1, 1];
fileName = 'ani-' + animetionPattern + '-' + fileName + '.gif';
console.log(scaleCanvas);
pngArea.innerHTML = '';
image.src = dataURL || dataURLDefault;
image.width = newImage.naturalWidth;
image.height = newImage.naturalHeight;
//縮小
if(image.naturalWidth>800 && image.naturalHeight>800){
canvas.width = image.width/10;
canvas.height = image.height/10;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0, image.width, image.height,
0, 0, canvas.width, canvas.height);
image.src = canvas.toDataURL();
image.width = canvas.width;
image.height = canvas.height;
}
console.log(image.width, image.height);
canvas.width = image.width * scaleCanvas[0];
canvas.height = image.height * scaleCanvas[1];
ctx = canvas.getContext('2d');
ctx.fillStyle = background;
var drawAnimateImage = function(frame){
if(!frame){return;}
var scaleX=frame[0], scaleY=frame[1],
dpX=frame[2], dpY=frame[3], rot=frame[4] || 0;
var dX = image.width * dpX;
var dY = image.height * dpY;
ctx.translate(0, 0);
ctx.setTransform(1,0,0,1,0,0);
ctx.rotate(0);
//ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0,0,canvas.width, canvas.height);
if(rot){
ctx.translate(canvas.width / 2, canvas.height / 2);
var rad = rot * Math.PI /180;
ctx.rotate(rad);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
} else {
ctx.setTransform(scaleX,0,0,scaleY,dX,dY);
}
x = (canvas.width - image.width * scaleX) / 2 / scaleX;
y = (canvas.height - image.height * scaleY) / 2 / scaleY;
ctx.drawImage(image, x, y);
var png = new Image();
png.setAttribute('class', 'png');
pngArea.appendChild(png);
png.src = canvas.toDataURL();
}
console.log([image.width,image.height]);
for(var i=0,len=frames[animetionPattern].length;i<len;i++){
drawAnimateImage(frames[animetionPattern][i]);
}
//複数画像のnaturalWidthを正常に取得するため、タイマー処理を行う
setTimeout(function(){
createGif();
},1000);
}
//動画gif生成
//https://jnordberg.github.io/gif.js/
var createGif = function(){
var pngs = document.getElementsByClassName('png') || [];
var gifArea = document.getElementById('gifArea');
var width = pngs[0].naturalWidth || 200;
var height = pngs[0].naturalHeight || 200;
var quality = $('[name="quality"]').val() || 10;
var gif = new GIF({
width:width,
height:height,
workers: 2,
quality: quality,
background: '#ffffff',
//transparent: '0x00FF00',
});
for(var i=0,len=pngs.length;i<len;i++){
gif.addFrame(pngs[i], {delay: 100});
}
gif.on('finished', function(blob) {
var uri = URL.createObjectURL(blob);
//window.open(uri, 'ani-gif');
blobToDataURL(blob, function(dataURL){
var img = new Image();
var a = document.createElement('a');
a.setAttribute('title',fileName);
a.setAttribute('download',fileName);
a.setAttribute('href',dataURL);
img.src= dataURL;
gifArea.appendChild(a);
a.appendChild(img);
});
});
var blobToDataURL = function(blob, callback) {
var fr = new FileReader();
fr.onload = function(e) {callback(e.target.result);}
fr.readAsDataURL(blob);
}
gif.render();
}
})();
CSS
#dragArea.drag{
background-color:#ffaaaa;
}
.area{
min-height:80px;
}
canvas{
border:1px solid #ffcccc;
}
canvas,
img{
min-width:100px;
min-height:100px;
max-width:200px;
max-height:200px;
}
input[type="color"],
label{
display:inline-block;
padding:6px 12px;
margin:4px 4px;
border:1px solid #000000;
border-radius:4px;
cursor:pointer;
}
label.selected,
label:hover{
background-color:#ffaaaa;
}
input[type="radio"]{
margin:4px;
display:none;
}
input[type="file"]{
display:none;
}
#code{
display:none;
}
HTML
ページのソースを表示 : Ctrl+U , DevTools : F12
view-source:https://hi0a.com/demo/-js/js-canvas-to-animation-gif/
ABOUT
hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。
プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。
必要な機能の関数をコピペ利用したり勉強に活用できます。
プログラムの動作サンプル結果は画面上部に出力表示されています。
環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。
画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。
動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。
途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。