以下のエディターはJavaScriptフレームアニメーションを提供します(例を使って説明)。編集者はこれがとても良いものだと思ったので、皆さんの参考として今から共有します。エディターをフォローして見てみましょう
前に述べたように
フレームアニメーションは、アニメーションアクションを「連続したキーフレーム」に分解すること、つまり、タイムラインの各フレームにフレームごとに異なるコンテンツを描画することです。継続的に再生されるアニメーション。フレームアニメーションはフレームごとに描画されるため、自由度が高く、表現したい内容をほぼすべて表現することができます。この記事ではJavaScriptのフレームアニメーションについて詳しく紹介します
概要
[分類]
フレームアニメーションにはgif、CSS3アニメーション、JavaScriptの3つの一般的な方法があります
gitやCSS3アニメーションでは一時停止を柔軟に制御できませんアニメーションの再生やフレームアニメーションの展開をより柔軟に行うことができません。また、GIF 画像ではアニメーションの完了イベントをキャプチャできません。そのため、一般的にはJavaScriptを使ってフレームアニメーションを実装します
【原理】
jsでフレームアニメーションを実装するには2つの方法があります
1 複数のフレームアニメーション画像がある場合、imageタグを使用してそのフレームアニメーションを運ぶことができます。画像の src 属性を定期的に変更します (非推奨)
2. 画像内にすべてのアニメーション キー フレームを描画し、その画像を要素の背景画像として使用し、要素の背景位置属性を定期的に変更します。 (推奨)
1つ目の方法では複数のHTTPリクエストを使用する必要があるため、一般的には2つ目の方法を使用することを推奨します
【例】
以下はフレームアニメーションの使用例です
<p id="rabbit" ></p> <button id="btn">暂停运动</button> <script> var url = 'rabbit-big.png'; var positions = ['0,-854','-174 -852','-349 -852','-524 -852','-698 -852','-873 -848']; var ele = document.getElementById('rabbit'); var oTimer = null; btn.onclick = function(){ if(btn.innerHTML == '开始运动'){ frameAnimation(ele,positions,url); btn.innerHTML = '暂停运动'; }else{ clearTimeout(oTimer); btn.innerHTML = '开始运动'; } } frameAnimation(ele,positions,url); function frameAnimation(ele,positions,url){ ele.style.backgroundImage = 'url(' + url + ')'; ele.style.backgroundRepeat = 'no-repeat'; var index = 0; function run(){ var pos = positions[index].split(' '); ele.style.backgroundPosition = pos[0] + 'px ' + pos[1] + 'px'; index++; if(index >= positions.length){ index = 0; } oTimer = setTimeout(run,80); } run(); } </script>
一般的なフレームアニメーション
一般的なフレームアニメーションライブラリを設計してみましょう
[要件分析]
1. 画像のプリロードをサポートします
2. 2つのアニメーション再生方法をサポートし、アニメーションの各フレームをカスタマイズします
3. 単一グループをサポートしますアニメーション ループの数を制御します (無制限の回数をサポートできます)
4. 一連のアニメーションを完了し、次の一連のアニメーションに進むことをサポートします
5. 各アニメーションが完了した後の待機時間をサポートします
6. アニメーションをサポートします再生の一時停止と再開
7、アニメーション完了後のコールバック関数の実行をサポートします
【プログラミングインターフェース】
1.loadImage(imglist)//画像をプリロードします
2.changePosition(ele,positions,imageUrl) //要素の背景位置を変更することでアニメーションを実現
3.changeSrc(ele,imglist)//画像要素のsrcを変更することで実現
4.enterFrame(callback)//各フレームで実行される関数アニメーションは、ユーザーがアニメーションの各フレームのコールバックをカスタマイズできることに相当します
5.repeat(times)//アニメーションが繰り返される回数が空の場合、無制限の回数を意味します
6. //最後のアニメーションを無限に繰り返します。これは、repeat() と同等です
7. wait(time)/ /各アニメーションの実行が完了した後の待機時間
8, then(callback)//アニメーション後のコールバック関数実行が完了しました
9, start(interval)//アニメーションが実行を開始します、intervalはアニメーション実行の間隔を表します
10、pause()//アニメーションを一時停止します
11.前の一時停止ポイントからのアニメーション
12. destroy()//リソースを解放します
[メソッドの呼び出し]
チェーン呼び出しをサポートし、動詞を使用してインターフェイスを記述します
【コード設計】
1.画像のプリロード→アニメーションの実行→アニメーションの終了といったタスクチェーンになります。タスク チェーンには、同期実行と非同期スケジュール実行の 2 つのタスクが含まれます
2. 現在のタスク チェーンのインデックスを記録します
3. 各タスクの実行後、next メソッドを呼び出して次のタスクを実行し、タスク チェーンを更新します。インデックス値も同時に取得
【インターフェース定義】
'use strict'; /* 帧动画库类 * @constructor */ function FrameAnimation(){} /* 添加一个同步任务,去预加载图片 * @param imglist 图片数组 */ FrameAnimation.prototype.loadImage = function(imglist){} /* 添加一个异步定时任务,通过定时改变图片背景位置,实现帧动画 * @param ele dom对象 * @param positions 背景位置数组 * @param imageUrl 图片URL地址 */ FrameAnimation.prototype.changePosition = function(ele,positions,imageUrl){} /* 添加一个异步定时任务,通过定时改变image标签的src属性,实现帧动画 * @param ele dom对象 * @param imglist 图片数组 */ FrameAnimation.prototype.changeSrc = function(ele,imglist){} /* 添加一个异步定时任务,自定义动画每帧执行的任务函数 * @param tastFn 自定义每帧执行的任务函数 */ FrameAnimation.prototype.enterFrame = function(taskFn){} /* 添加一个同步任务,在上一个任务完成后执行回调函数 * @param callback 回调函数 */ FrameAnimation.prototype.then = function(callback){} /* 开始执行任务,异步定时任务执行的间隔 * @param interval */ FrameAnimation.prototype.start = function(interval){} /* 添加一个同步任务,回退到上一个任务,实现重复上一个任务的效果,可以定义重复的次数 * @param times 重复次数 */ FrameAnimation.prototype.repeat = function(times){} /* 添加一个同步任务,相当于repeat(),无限循环上一次任务 * */ FrameAnimation.prototype.repeatForever = function(){} /* 设置当前任务执行结束后到下一个任务开始前的等待时间 * @param time 等待时长 */ FrameAnimation.prototype.wait = function(time){} /* 暂停当前异步定时任务 * */ FrameAnimation.prototype.pause = function(){} /* 重新执行上一次暂停的异步定时任务 * */ FrameAnimation.prototype.restart = function(){} /* 释放资源 * */ FrameAnimation.prototype.dispose = function(){}
画像のプリロード
画像のプリロードは比較的独立した関数であり、モジュールimageloader.js
としてカプセル化できます。
'use strict'; /** * 预加载图片函数 * @param images 加载图片的数组或者对象 * @param callback 全部图片加载完毕后调用的回调函数 * @param timeout 加载超时的时长 */ function loadImage(images,callback,timeout){ //加载完成图片的计数器 var count = 0; //全部图片加载成功的标志位 var success = true; //超时timer的id var timeoutId = 0; //是否加载超时的标志位 var isTimeout = false; //对图片数组(或对象)进行遍历 for(var key in images){ //过滤prototype上的属性 if(!images.hasOwnProperty(key)){ continue; } //获得每个图片元素 //期望格式是object:{src:xxx} var item = images[key]; if(typeof item === 'string'){ item = images[key] = { src:item }; } //如果格式不满足期望,则丢弃此条数据,进行下一次遍历 if(!item || !item.src){ continue; } //计数+1 count++; //设置图片元素的id item.id = '__img__' + key + getId(); //设置图片元素的img,它是一个Image对象 item.img = window[item.id] = new Image(); doLoad(item); } //遍历完成如果计数为0,则直接调用callback if(!count){ callback(success); }else if(timeout){ timeoutId = setTimeout(onTimeout,timeout); } /** * 真正进行图片加载的函数 * @param item 图片元素对象 */ function doLoad(item){ item.status = 'loading'; var img = item.img; //定义图片加载成功的回调函数 img.onload = function(){ success = success && true; item.status = 'loaded'; done(); } //定义图片加载失败的回调函数 img.onerror = function(){ success = false; item.status = 'error'; done(); } //发起一个http(s)请求 img.src = item.src; /** * 每张图片加载完成的回调函数 */ function done(){ img.onload = img.onerror = null; try{ delete window[item.id]; }catch(e){ } //每张图片加载完成,计数器减1,当所有图片加载完成,且没有超时的情况,清除超时计时器,且执行回调函数 if(!--count && !isTimeout){ clearTimeout(timeoutId); callback(success); } } } /** * 超时函数 */ function onTimeout(){ isTimeout = true; callback(false); } } var __id = 0; function getId(){ return ++__id; } module.exports = loadImage;
Timeline
アニメーション処理ではsetTimeout()を繰り返し使用することで実装されていますが、この間隔は正確ではありません。次に、タイムラインクラスのタイムラインを実装しましょう。JS
Animationクラスの実装
アニメーションクラスanimation.js
webpackconfigurationの完全なコードは次のとおりです。 アニメーション フレーム アニメーション ライブラリの制作には AMD モジュール仕様が使用されますが、ブラウザ レベルではサポートされていないため、モジュール管理には webpack を使用する必要があり、animation.js、imageloader.js、timeline.js を使用する必要があります。は 1 つのファイルにパッケージ化されています
'use strict'; var DEFAULT_INTERVAL = 1000/60; //初始化状态 var STATE_INITIAL = 0; //开始状态 var STATE_START = 1; //停止状态 var STATE_STOP = 2; var requestAnimationFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame|| window.mozRequestAnimationFrame || window.oRequestAnimationFrame || function(callback){ return window.setTimeout(callback,(callback.interval || DEFAULT_INTERVAL)); } })(); var cancelAnimationFrame = (function(){ return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function(id){ return window.clearTimeout(id); } })(); /** * 时间轴类 * @constructor */ function Timeline(){ this.animationHandler = 0; this.state = STATE_INITIAL; } /** * 时间轴上每一次回调执行的函数 * @param time 从动画开始到当前执行的时间 */ Timeline.prototype.onenterframe = function(time){ } /** * 动画开始 * @param interval 每一次回调的间隔时间 */ Timeline.prototype.start = function(interval){ if(this.state === STATE_START){ return; } this.state = STATE_START; this.interval = interval || DEFAULT_INTERVAL; startTimeline(this,+new Date()); } /** * 动画停止 */ Timeline.prototype.stop = function(){ if(this.state !== STATE_START){ return; } this.state = STATE_STOP; //如果动画开始过,则记录动画从开始到现在所经历的时间 if(this.startTime){ this.dur = +new Date() - this.startTime; } cancelAnimationFrame(this.animationHandler); } /** * 重新开始动画 */ Timeline.prototype.restart = function(){ if(this.state === STATE_START){ return; } if(!this.dur || !this.interval){ return; } this.state = STATE_START; //无缝连接动画 startTimeline(this,+new Date()-this.dur); } /** * 时间轴动画启动函数 * @param timeline 时间轴的实例 * @param startTime 动画开始时间戳 */ function startTimeline(timeline,startTime){ //记录上一次回调的时间戳 var lastTick = +new Date(); timeline.startTime = startTime; nextTick.interval = timeline.interval; nextTick(); /** * 每一帧执行的函数 */ function nextTick(){ var now = +new Date(); timeline.animationHandler = requestAnimationFrame(nextTick); //如果当前时间与上一次回调的时间戳大于设置的时间间隔,表示这一次可以执行回调函数 if(now - lastTick >= timeline.interval){ timeline.onenterframe(now - startTime); lastTick = now; } } } module.exports = Timeline;
'use strict'; var loadImage = require('./imageloader'); var Timeline = require('./timeline'); //初始化状态 var STATE_INITIAL = 0; //开始状态 var STATE_START = 1; //停止状态 var STATE_STOP = 2; //同步任务 var TASK_SYNC = 0; //异步任务 var TASK_ASYNC = 1; /** * 简单的函数封装,执行callback * @param callback 执行函数 */ function next(callback){ callback && callback(); } /* 帧动画库类 * @constructor */ function FrameAnimation(){ this.taskQueue = []; this.index = 0; this.timeline = new Timeline(); this.state = STATE_INITIAL; } /* 添加一个同步任务,去预加载图片 * @param imglist 图片数组 */ FrameAnimation.prototype.loadImage = function(imglist){ var taskFn = function(next){ loadImage(imglist.slice(),next); }; var type = TASK_SYNC; return this._add(taskFn,type); } /* 添加一个异步定时任务,通过定时改变图片背景位置,实现帧动画 * @param ele dom对象 * @param positions 背景位置数组 * @param imageUrl 图片URL地址 */ FrameAnimation.prototype.changePosition = function(ele,positions,imageUrl){ var len = positions.length; var taskFn; var type; if(len){ var me = this; taskFn = function(next,time){ if(imageUrl){ ele.style.backgroundImage = 'url(' + imageUrl + ')'; } //获得当前背景图片位置索引 var index = Math.min(time/me.interval|0,len); var position = positions[index-1].split(' '); //改变dom对象的背景图片位置 ele.style.backgroundPosition = position[0] + 'px ' + position[1] + 'px'; if(index === len){ next(); } } type = TASK_ASYNC; }else{ taskFn = next; type = TASK_SYNC; } return this._add(taskFn,type); } /* 添加一个异步定时任务,通过定时改变image标签的src属性,实现帧动画 * @param ele dom对象 * @param imglist 图片数组 */ FrameAnimation.prototype.changeSrc = function(ele,imglist){ var len = imglist.length; var taskFn; var type; if(len){ var me = this; taskFn = function(next,time){ //获得当前背景图片位置索引 var index = Math.min(time/me.interval|0,len); //改变image对象的背景图片位置 ele.src = imglist[index-1]; if(index === len){ next(); } } type = TASK_ASYNC; }else{ taskFn = next; type = TASK_SYNC; } return this._add(taskFn,type); } /* 添加一个异步定时任务,自定义动画每帧执行的任务函数 * @param tastFn 自定义每帧执行的任务函数 */ FrameAnimation.prototype.enterFrame = function(taskFn){ return this._add(taskFn,TASK_ASYNC); } /* 添加一个同步任务,在上一个任务完成后执行回调函数 * @param callback 回调函数 */ FrameAnimation.prototype.then = function(callback){ var taskFn = function(next){ callback(this); next(); }; var type = TASK_SYNC; return this._add(taskFn,type); } /* 开始执行任务,异步定义任务执行的间隔 * @param interval */ FrameAnimation.prototype.start = function(interval){ if(this.state === STATE_START){ return this; } //如果任务链中没有任务,则返回 if(!this.taskQueue.length){ return this; } this.state = STATE_START; this.interval = interval; this._runTask(); return this; } /* 添加一个同步任务,回退到上一个任务,实现重复上一个任务的效果,可以定义重复的次数 * @param times 重复次数 */ FrameAnimation.prototype.repeat = function(times){ var me = this; var taskFn = function(){ if(typeof times === 'undefined'){ //无限回退到上一个任务 me.index--; me._runTask(); return; } if(times){ times--; //回退 me.index--; me._runTask(); }else{ //达到重复次数,跳转到下一个任务 var task = me.taskQueue[me.index]; me._next(task); } } var type = TASK_SYNC; return this._add(taskFn,type); } /* 添加一个同步任务,相当于repeat(),无限循环上一次任务 * */ FrameAnimation.prototype.repeatForever = function(){ return this.repeat(); } /* 设置当前任务执行结束后到下一个任务开始前的等待时间 * @param time 等待时长 */ FrameAnimation.prototype.wait = function(time){ if(this.taskQueue && this.taskQueue.length > 0){ this.taskQueue[this.taskQueue.length - 1].wait = time; } return this; } /* 暂停当前异步定时任务 * */ FrameAnimation.prototype.pause = function(){ if(this.state === STATE_START){ this.state = STATE_STOP; this.timeline.stop(); return this; } return this; } /* 重新执行上一次暂停的异步定时任务 * */ FrameAnimation.prototype.restart = function(){ if(this.state === STATE_STOP){ this.state = STATE_START; this.timeline.restart(); return this; } return this; } /* 释放资源 * */ FrameAnimation.prototype.dispose = function(){ if(this.state !== STATE_INITIAL){ this.state = STATE_INITIAL; this.taskQueue = null; this.timeline.stop(); this.timeline = null; return this; } return this; } /** * 添加一个任务到任务队列 * @param taskFn 任务方法 * @param type 任务类型 * @private */ FrameAnimation.prototype._add = function(taskFn,type){ this.taskQueue.push({ taskFn:taskFn, type:type }); return this; } /** * 执行任务 * @private */ FrameAnimation.prototype._runTask = function(){ if(!this.taskQueue || this.state !== STATE_START){ return; } //任务执行完毕 if(this.index === this.taskQueue.length){ this.dispose(); return; } //获得任务链上的当前任务 var task = this.taskQueue[this.index]; if(task.type === TASK_SYNC){ this._syncTask(task); }else{ this._asyncTask(task); } } /** * 同步任务 * @param task 执行的任务对象 * @private */ FrameAnimation.prototype._syncTask = function(task){ var me = this; var next = function(){ //切换到下一个任务 me._next(task); } var taskFn = task.taskFn; taskFn(next); } /** * 异步任务 * @param task 执行的任务对象 * @private */ FrameAnimation.prototype._asyncTask = function(task){ var me = this; //定义每一帧执行的回调函数 var enterframe = function(time){ var taskFn = task.taskFn; var next = function(){ //停止当前任务 me.timeline.stop(); //执行下一个任务 me._next(task); }; taskFn(next,time); } this.timeline.onenterframe = enterframe; this.timeline.start(this.interval); } /** * 切换到下一个任务,支持如果当前任务需要等待,则延时执行 * @private */ FrameAnimation.prototype._next = function(task){ this.index++; var me = this; task.wait ? setTimeout(function(){ me._runTask(); },task.wait) : this._runTask(); } module.exports = function(){ return new FrameAnimation(); }
その他の例効果を実現するだけでなくウサギのカートのフレームアニメーションを使用することもできますウサギの勝利とウサギの失敗の効果を実現します
module.exports = { entry:{ animation:"./src/animation.js" }, output:{ path:__dirname + "/build", filename:"[name].js", library:"animation", libraryTarget:"umd", } }
以上がJavaScriptのフレームアニメーションを説明する例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。