@ -0,0 +1,7 @@ |
||||
#kof { |
||||
width: 960px; |
||||
height: 540px; |
||||
background-image: url('/static/images/background/0.gif'); |
||||
background-size: 200% 100%; |
||||
background-position: top; |
||||
} |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,51 @@ |
||||
let AC_GAME_OBJECTS = []; |
||||
|
||||
class AcGameObject { |
||||
constructor() { |
||||
AC_GAME_OBJECTS.push(this); |
||||
this.time_delta = 0; // 距离上一帧的事件间隔
|
||||
this.has_call_start = false; // 是否调用过start函数
|
||||
} |
||||
|
||||
start() { // 最开始执行一次
|
||||
|
||||
} |
||||
|
||||
update() { // 每一帧执行一次(除了第一帧)
|
||||
|
||||
} |
||||
|
||||
destory() {
|
||||
for (let i in AC_GAME_OBJECTS) { |
||||
if (AC_GAME_OBJECTS[i] === this) { |
||||
AC_GAME_OBJECTS.splice(i,1); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
let last_timestamp; // 上一帧执行完的时刻
|
||||
|
||||
// timestamp: 当前时刻
|
||||
let AC_GAME_OBJECT_FRAME = (timestamp) => { |
||||
for (let obj of AC_GAME_OBJECTS) { // 所有对象执行一次start函数
|
||||
if (obj.has_call_start === false) { |
||||
obj.start(); |
||||
obj.has_call_start = true; |
||||
} |
||||
else { |
||||
obj.time_delta = timestamp - last_timestamp; |
||||
obj.update(); |
||||
} |
||||
} |
||||
last_timestamp = timestamp; |
||||
requestAnimationFrame(AC_GAME_OBJECT_FRAME); // 递归执行该函数
|
||||
} |
||||
|
||||
requestAnimationFrame(AC_GAME_OBJECT_FRAME); |
||||
|
||||
export { |
||||
AcGameObject, |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { GameMap } from '/static/js/game_map/base.js' |
||||
import { Kyo } from './player/kyo.js'; |
||||
|
||||
|
||||
class KOF{ |
||||
constructor(id) { |
||||
this.$kof = $('#' + id); |
||||
this.gamemap = new GameMap(this); // root都是指kof对象
|
||||
this.players = [ |
||||
new Kyo(this,{ |
||||
id: 0,
|
||||
x: 100, |
||||
y: 0, |
||||
width: 120, |
||||
height: 200, |
||||
color: 'blue', |
||||
}), |
||||
new Kyo(this,{ |
||||
id: 1,
|
||||
x: 740, |
||||
y: 0, |
||||
width: 120, |
||||
height: 200, |
||||
color: 'red', |
||||
}) |
||||
] |
||||
} |
||||
} |
||||
|
||||
export { |
||||
KOF, |
||||
} |
@ -0,0 +1,18 @@ |
||||
export class Controller { |
||||
constructor($canvas) { |
||||
this.$canvas = $canvas; |
||||
this.press_keys = new Set(); |
||||
this.start(); |
||||
} |
||||
|
||||
start() { // 自定义按键事件,保证按下一个键只存一次键值
|
||||
let outer = this; |
||||
this.$canvas.keydown(function(e) { |
||||
outer.press_keys.add(e.key); |
||||
});
|
||||
|
||||
this.$canvas.keyup(function(e) { |
||||
outer.press_keys.delete(e.key); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
import { AcGameObject } from '/static/js/ac_game_object/base.js' |
||||
import { Controller } from '../controller/base.js'; |
||||
|
||||
class GameMap extends AcGameObject { |
||||
constructor(root) { |
||||
super(); |
||||
this.root = root; // KOF对象
|
||||
this.$canvas = $('<canvas width="960" height="540" tabindex=0></canvas>'); |
||||
this.ctx = this.$canvas[0].getContext('2d'); // 获取上下文,canvas对象
|
||||
|
||||
this.root.$kof.append(this.$canvas); // 将canvas加入$kof中,this.root.$kof是class=kof的div
|
||||
this.$canvas.focus(); // 聚焦
|
||||
|
||||
this.controller = new Controller(this.$canvas); |
||||
|
||||
} |
||||
|
||||
start() { |
||||
|
||||
} |
||||
|
||||
update() { |
||||
this.render(); |
||||
} |
||||
|
||||
render() { |
||||
this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height); |
||||
// console.log(this.ctx.canvas.width,this.ctx.canvas.height); // 打印canvas的宽高
|
||||
// console.log(this.$canvas.width());
|
||||
// this.ctx.fillStyle = "#21252b"; // 画布的颜色
|
||||
// // this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height); // 画矩形
|
||||
// this.ctx.fillRect(0,0,this.$canvas.width(),this.$canvas.height());
|
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
export { |
||||
GameMap, |
||||
} |
@ -0,0 +1,162 @@ |
||||
import { AcGameObject } from "../ac_game_object/base.js"; |
||||
|
||||
export class Player extends AcGameObject { |
||||
constructor(root, info) { |
||||
super(); |
||||
|
||||
this.root = root; |
||||
this.id = info.id; // 人物id
|
||||
this.x = info.x; // 人物坐标
|
||||
this.y = info.y; |
||||
this.width = info.width; // 人物大小
|
||||
this.height = info.height; |
||||
this.color = info.color; // 人物颜色
|
||||
|
||||
this.vx = 0; // 横纵方向的移动速度
|
||||
this.vy = 0; |
||||
|
||||
this.speedx = 400; // 水平速度初始速度
|
||||
this.speedy = -1000; // 跳起的初始速度
|
||||
|
||||
this.gravity = 50; // 重力加速度
|
||||
this.ctx = this.root.gamemap.ctx; // 获取canvas对象
|
||||
|
||||
|
||||
this.direction = 1; // 1:向右 -1:向左
|
||||
|
||||
|
||||
// 0:idle 1:向前 2:向后 3:跳跃 4:攻击 5:被打 6: 死亡
|
||||
this.status = 3; // 人物当前状态(总共七种状态)
|
||||
|
||||
this.press_keys = this.root.gamemap.controller.press_keys; // 用户按下的所有键值
|
||||
this.animations = new Map(); // 每个动作动画的配置
|
||||
this.current_frame_cnt = 0; // 当前加载了多少帧
|
||||
} |
||||
|
||||
start() { |
||||
|
||||
} |
||||
|
||||
update_controll() { |
||||
let w, a, d, space; |
||||
// 两个用户使用不同的按键
|
||||
if (this.id === 0) { |
||||
w = this.press_keys.has('w'); // 跳
|
||||
a = this.press_keys.has('a'); // 左移
|
||||
d = this.press_keys.has('d'); // 右移
|
||||
space = this.press_keys.has(' '); // 停
|
||||
} else { |
||||
w = this.press_keys.has('ArrowUp'); |
||||
a = this.press_keys.has('ArrowLeft'); |
||||
d = this.press_keys.has('ArrowRight'); |
||||
space = this.press_keys.has('Enter'); |
||||
} |
||||
|
||||
// 0表示静止 1表示移动 3表示跳跃
|
||||
if (this.status === 0 || this.status === 1) { |
||||
if (space) { // 攻击状态
|
||||
this.status = 4; |
||||
this.vx = 0; |
||||
this.current_frame_cnt = 0; |
||||
} |
||||
else if (w) { // 如果按的是跳
|
||||
if (d) { |
||||
this.vx = this.speedx; |
||||
} else if (a) { |
||||
this.vx = -this.speedx; |
||||
} else { |
||||
this.vx = 0; |
||||
} |
||||
this.vy = this.speedy; |
||||
this.status = 3; |
||||
this.current_frame_cnt = 0; |
||||
} else if (d) { |
||||
this.vx = this.speedx; |
||||
this.status = 1; |
||||
} else if (a) { |
||||
this.vx = -this.speedx; |
||||
this.status = 1; |
||||
} else { |
||||
this.vx = 0; |
||||
this.status = 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
update_direction() { // 保证两个人物是对称的
|
||||
let players = this.root.players; |
||||
if (players[0] && players[1]) { |
||||
let me = this, you = players[1 - this.id]; |
||||
if (me.x < you.x) me.direction = 1; // 向右
|
||||
else me.direction = -1; // 向左
|
||||
} |
||||
} |
||||
|
||||
update_move() { |
||||
this.vy += this.gravity; |
||||
|
||||
// 改变x,y轴坐标
|
||||
this.x += this.vx * this.time_delta / 1000; |
||||
this.y += this.vy * this.time_delta / 1000; |
||||
|
||||
|
||||
if (this.y > 300) { |
||||
this.y = 300; |
||||
if (this.status === 3) this.status = 0; |
||||
} |
||||
|
||||
if (this.x < 0) { |
||||
this.x = 0; |
||||
} else if (this.x + this.width > this.root.gamemap.$canvas.width()) { |
||||
this.x = this.root.gamemap.$canvas.width() - this.width; |
||||
} |
||||
|
||||
} |
||||
|
||||
update() { |
||||
this.update_controll(); |
||||
this.update_move(); |
||||
this.update_direction(); |
||||
|
||||
this.render(); |
||||
} |
||||
|
||||
render() { |
||||
// this.ctx.fillStyle = this.color; // 画布的颜色
|
||||
// this.ctx.fillRect(this.x, this.y, this.width, this.height); // 画人物
|
||||
|
||||
let status = this.status; |
||||
|
||||
if (this.status === 1 && this.direction * this.vx < 0) status = 2; |
||||
|
||||
// 根据状态获取动画
|
||||
let obj = this.animations.get(status); |
||||
if (obj && obj.loaded) { |
||||
if (this.direction > 0) { |
||||
let k = parseInt(this.current_frame_cnt / obj.frame_rate) % obj.frame_cnt; // 每5帧刷新一次
|
||||
let image = obj.gif.frames[k].image; // 渲染第k张图片
|
||||
this.ctx.drawImage(image, this.x, this.y + obj.offset_y, image.width * obj.scale, image.height * obj.scale); |
||||
} else { |
||||
this.ctx.save(); |
||||
this.ctx.scale(-1, 1); |
||||
this.ctx.translate(-this.root.gamemap.$canvas.width(), 0); |
||||
|
||||
let k = parseInt(this.current_frame_cnt / obj.frame_rate) % obj.frame_cnt; // 每5帧刷新一次
|
||||
let image = obj.gif.frames[k].image; // 渲染第k张图片
|
||||
this.ctx.drawImage(image, this.root.gamemap.$canvas.width() - this.x - this.width, this.y + obj.offset_y, image.width * obj.scale, image.height * obj.scale); |
||||
|
||||
this.ctx.restore(); |
||||
} |
||||
|
||||
} |
||||
|
||||
if (status === 4) { |
||||
if (this.current_frame_cnt === obj.frame_rate * (obj.frame_cnt - 1)) { |
||||
this.status = 0; |
||||
} |
||||
} |
||||
|
||||
this.current_frame_cnt++; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,39 @@ |
||||
import { Player } from "./base.js"; |
||||
import { GIF } from '/static/js/utils/gif.js'; |
||||
export class Kyo extends Player{ |
||||
constructor(root,info) { |
||||
super(root,info); |
||||
this.init_animations(); |
||||
} |
||||
|
||||
init_animations() { |
||||
let outer = this; // 要在下面的function中使用必须保存当前的this
|
||||
let offsets = [0,-10,-10,0,0,0,0]; |
||||
// 总共有七个动作
|
||||
for (let i = 0 ; i < 7; i++) { |
||||
let gif = new GIF(); |
||||
gif.load(`/static/images/player/kyo/${i}.gif`); // gif->images
|
||||
this.animations.set(i, { |
||||
gif: gif, |
||||
frame_cnt: 0, // 总图片数
|
||||
frame_rate: 5, // 每5帧过渡一次
|
||||
offset_y: offsets[i], // y方向偏移量
|
||||
loaded: false, // 是否加载完成
|
||||
scale: 2, // 放大多少倍
|
||||
}); |
||||
|
||||
|
||||
|
||||
// 加载动画
|
||||
gif.onload = function() { |
||||
let obj = outer.animations.get(i); // 获取第i个动作
|
||||
obj.frame_cnt = gif.frames.length; // 配置总图片数
|
||||
obj.loaded = true; // 成功加载
|
||||
|
||||
if (i === 3) { |
||||
obj.frame_rate = 4; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,376 @@ |
||||
const GIF = function () { |
||||
// **NOT** for commercial use.
|
||||
var timerID; // timer handle for set time out usage
|
||||
var st; // holds the stream object when loading.
|
||||
var interlaceOffsets = [0, 4, 2, 1]; // used in de-interlacing.
|
||||
var interlaceSteps = [8, 8, 4, 2]; |
||||
var interlacedBufSize; // this holds a buffer to de interlace. Created on the first frame and when size changed
|
||||
var deinterlaceBuf; |
||||
var pixelBufSize; // this holds a buffer for pixels. Created on the first frame and when size changed
|
||||
var pixelBuf; |
||||
const GIF_FILE = { // gif file data headers
|
||||
GCExt: 0xF9, |
||||
COMMENT: 0xFE, |
||||
APPExt: 0xFF, |
||||
UNKNOWN: 0x01, // not sure what this is but need to skip it in parser
|
||||
IMAGE: 0x2C, |
||||
EOF: 59, // This is entered as decimal
|
||||
EXT: 0x21, |
||||
}; |
||||
// simple buffered stream used to read from the file
|
||||
var Stream = function (data) { |
||||
this.data = new Uint8ClampedArray(data); |
||||
this.pos = 0; |
||||
var len = this.data.length; |
||||
this.getString = function (count) { // returns a string from current pos of len count
|
||||
var s = ""; |
||||
while (count--) { s += String.fromCharCode(this.data[this.pos++]) } |
||||
return s; |
||||
}; |
||||
this.readSubBlocks = function () { // reads a set of blocks as a string
|
||||
var size, count, data = ""; |
||||
do { |
||||
count = size = this.data[this.pos++]; |
||||
while (count--) { data += String.fromCharCode(this.data[this.pos++]) } |
||||
} while (size !== 0 && this.pos < len); |
||||
return data; |
||||
} |
||||
this.readSubBlocksB = function () { // reads a set of blocks as binary
|
||||
var size, count, data = []; |
||||
do { |
||||
count = size = this.data[this.pos++]; |
||||
while (count--) { data.push(this.data[this.pos++]); } |
||||
} while (size !== 0 && this.pos < len); |
||||
return data; |
||||
} |
||||
}; |
||||
// LZW decoder uncompressed each frames pixels
|
||||
// this needs to be optimised.
|
||||
// minSize is the min dictionary as powers of two
|
||||
// size and data is the compressed pixels
|
||||
function lzwDecode(minSize, data) { |
||||
var i, pixelPos, pos, clear, eod, size, done, dic, code, last, d, len; |
||||
pos = pixelPos = 0; |
||||
dic = []; |
||||
clear = 1 << minSize; |
||||
eod = clear + 1; |
||||
size = minSize + 1; |
||||
done = false; |
||||
while (!done) { // JavaScript optimisers like a clear exit though I never use 'done' apart from fooling the optimiser
|
||||
last = code; |
||||
code = 0; |
||||
for (i = 0; i < size; i++) { |
||||
if (data[pos >> 3] & (1 << (pos & 7))) { code |= 1 << i } |
||||
pos++; |
||||
} |
||||
if (code === clear) { // clear and reset the dictionary
|
||||
dic = []; |
||||
size = minSize + 1; |
||||
for (i = 0; i < clear; i++) { dic[i] = [i] } |
||||
dic[clear] = []; |
||||
dic[eod] = null; |
||||
} else { |
||||
if (code === eod) { done = true; return } |
||||
if (code >= dic.length) { dic.push(dic[last].concat(dic[last][0])) } |
||||
else if (last !== clear) { dic.push(dic[last].concat(dic[code][0])) } |
||||
d = dic[code]; |
||||
len = d.length; |
||||
for (i = 0; i < len; i++) { pixelBuf[pixelPos++] = d[i] } |
||||
if (dic.length === (1 << size) && size < 12) { size++ } |
||||
} |
||||
} |
||||
}; |
||||
function parseColourTable(count) { // get a colour table of length count Each entry is 3 bytes, for RGB.
|
||||
var colours = []; |
||||
for (var i = 0; i < count; i++) { colours.push([st.data[st.pos++], st.data[st.pos++], st.data[st.pos++]]) } |
||||
return colours; |
||||
} |
||||
function parse() { // read the header. This is the starting point of the decode and async calls parseBlock
|
||||
var bitField; |
||||
st.pos += 6; |
||||
gif.width = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
gif.height = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
bitField = st.data[st.pos++]; |
||||
gif.colorRes = (bitField & 0b1110000) >> 4; |
||||
gif.globalColourCount = 1 << ((bitField & 0b111) + 1); |
||||
gif.bgColourIndex = st.data[st.pos++]; |
||||
st.pos++; // ignoring pixel aspect ratio. if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
|
||||
if (bitField & 0b10000000) { gif.globalColourTable = parseColourTable(gif.globalColourCount) } // global colour flag
|
||||
setTimeout(parseBlock, 0); |
||||
} |
||||
function parseAppExt() { // get application specific data. Netscape added iterations and terminator. Ignoring that
|
||||
st.pos += 1; |
||||
if ('NETSCAPE' === st.getString(8)) { st.pos += 8 } // ignoring this data. iterations (word) and terminator (byte)
|
||||
else { |
||||
st.pos += 3; // 3 bytes of string usually "2.0" when identifier is NETSCAPE
|
||||
st.readSubBlocks(); // unknown app extension
|
||||
} |
||||
}; |
||||
function parseGCExt() { // get GC data
|
||||
var bitField; |
||||
st.pos++; |
||||
bitField = st.data[st.pos++]; |
||||
gif.disposalMethod = (bitField & 0b11100) >> 2; |
||||
gif.transparencyGiven = bitField & 0b1 ? true : false; // ignoring bit two that is marked as userInput???
|
||||
gif.delayTime = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
gif.transparencyIndex = st.data[st.pos++]; |
||||
st.pos++; |
||||
}; |
||||
function parseImg() { // decodes image data to create the indexed pixel image
|
||||
var deinterlace, frame, bitField; |
||||
deinterlace = function (width) { // de interlace pixel data if needed
|
||||
var lines, fromLine, pass, toline; |
||||
lines = pixelBufSize / width; |
||||
fromLine = 0; |
||||
if (interlacedBufSize !== pixelBufSize) { // create the buffer if size changed or undefined.
|
||||
deinterlaceBuf = new Uint8Array(pixelBufSize); |
||||
interlacedBufSize = pixelBufSize; |
||||
} |
||||
for (pass = 0; pass < 4; pass++) { |
||||
for (toLine = interlaceOffsets[pass]; toLine < lines; toLine += interlaceSteps[pass]) { |
||||
deinterlaceBuf.set(pixelBuf.subarray(fromLine, fromLine + width), toLine * width); |
||||
fromLine += width; |
||||
} |
||||
} |
||||
}; |
||||
frame = {} |
||||
gif.frames.push(frame); |
||||
frame.disposalMethod = gif.disposalMethod; |
||||
frame.time = gif.length; |
||||
frame.delay = gif.delayTime * 10; |
||||
gif.length += frame.delay; |
||||
if (gif.transparencyGiven) { frame.transparencyIndex = gif.transparencyIndex } |
||||
else { frame.transparencyIndex = undefined } |
||||
frame.leftPos = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
frame.topPos = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
frame.width = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
frame.height = (st.data[st.pos++]) + ((st.data[st.pos++]) << 8); |
||||
bitField = st.data[st.pos++]; |
||||
frame.localColourTableFlag = bitField & 0b10000000 ? true : false; |
||||
if (frame.localColourTableFlag) { frame.localColourTable = parseColourTable(1 << ((bitField & 0b111) + 1)) } |
||||
if (pixelBufSize !== frame.width * frame.height) { // create a pixel buffer if not yet created or if current frame size is different from previous
|
||||
pixelBuf = new Uint8Array(frame.width * frame.height); |
||||
pixelBufSize = frame.width * frame.height; |
||||
} |
||||
lzwDecode(st.data[st.pos++], st.readSubBlocksB()); // decode the pixels
|
||||
if (bitField & 0b1000000) { // de interlace if needed
|
||||
frame.interlaced = true; |
||||
deinterlace(frame.width); |
||||
} else { frame.interlaced = false } |
||||
processFrame(frame); // convert to canvas image
|
||||
}; |
||||
function processFrame(frame) { // creates a RGBA canvas image from the indexed pixel data.
|
||||
var ct, cData, dat, pixCount, ind, useT, i, pixel, pDat, col, frame, ti; |
||||
frame.image = document.createElement('canvas'); |
||||
frame.image.width = gif.width; |
||||
frame.image.height = gif.height; |
||||
frame.image.ctx = frame.image.getContext("2d"); |
||||
ct = frame.localColourTableFlag ? frame.localColourTable : gif.globalColourTable; |
||||
if (gif.lastFrame === null) { gif.lastFrame = frame } |
||||
useT = (gif.lastFrame.disposalMethod === 2 || gif.lastFrame.disposalMethod === 3) ? true : false; |
||||
if (!useT) { frame.image.ctx.drawImage(gif.lastFrame.image, 0, 0, gif.width, gif.height) } |
||||
cData = frame.image.ctx.getImageData(frame.leftPos, frame.topPos, frame.width, frame.height); |
||||
ti = frame.transparencyIndex; |
||||
dat = cData.data; |
||||
if (frame.interlaced) { pDat = deinterlaceBuf } |
||||
else { pDat = pixelBuf } |
||||
pixCount = pDat.length; |
||||
ind = 0; |
||||
for (i = 0; i < pixCount; i++) { |
||||
pixel = pDat[i]; |
||||
col = ct[pixel]; |
||||
if (ti !== pixel) { |
||||
dat[ind++] = col[0]; |
||||
dat[ind++] = col[1]; |
||||
dat[ind++] = col[2]; |
||||
dat[ind++] = 255; // Opaque.
|
||||
} else |
||||
if (useT) { |
||||
dat[ind + 3] = 0; // Transparent.
|
||||
ind += 4; |
||||
} else { ind += 4 } |
||||
} |
||||
frame.image.ctx.putImageData(cData, frame.leftPos, frame.topPos); |
||||
gif.lastFrame = frame; |
||||
if (!gif.waitTillDone && typeof gif.onload === "function") { doOnloadEvent() }// if !waitTillDone the call onload now after first frame is loaded
|
||||
}; |
||||
// **NOT** for commercial use.
|
||||
function finnished() { // called when the load has completed
|
||||
gif.loading = false; |
||||
gif.frameCount = gif.frames.length; |
||||
gif.lastFrame = null; |
||||
st = undefined; |
||||
gif.complete = true; |
||||
gif.disposalMethod = undefined; |
||||
gif.transparencyGiven = undefined; |
||||
gif.delayTime = undefined; |
||||
gif.transparencyIndex = undefined; |
||||
gif.waitTillDone = undefined; |
||||
pixelBuf = undefined; // dereference pixel buffer
|
||||
deinterlaceBuf = undefined; // dereference interlace buff (may or may not be used);
|
||||
pixelBufSize = undefined; |
||||
deinterlaceBuf = undefined; |
||||
gif.currentFrame = 0; |
||||
if (gif.frames.length > 0) { gif.image = gif.frames[0].image } |
||||
doOnloadEvent(); |
||||
if (typeof gif.onloadall === "function") { |
||||
(gif.onloadall.bind(gif))({ type: 'loadall', path: [gif] }); |
||||
} |
||||
if (gif.playOnLoad) { gif.play() } |
||||
} |
||||
function canceled() { // called if the load has been cancelled
|
||||
finnished(); |
||||
if (typeof gif.cancelCallback === "function") { (gif.cancelCallback.bind(gif))({ type: 'canceled', path: [gif] }) } |
||||
} |
||||
function parseExt() { // parse extended blocks
|
||||
const blockID = st.data[st.pos++]; |
||||
if (blockID === GIF_FILE.GCExt) { parseGCExt() } |
||||
else if (blockID === GIF_FILE.COMMENT) { gif.comment += st.readSubBlocks() } |
||||
else if (blockID === GIF_FILE.APPExt) { parseAppExt() } |
||||
else { |
||||
if (blockID === GIF_FILE.UNKNOWN) { st.pos += 13; } // skip unknow block
|
||||
st.readSubBlocks(); |
||||
} |
||||
|
||||
} |
||||
function parseBlock() { // parsing the blocks
|
||||
if (gif.cancel !== undefined && gif.cancel === true) { canceled(); return } |
||||
|
||||
const blockId = st.data[st.pos++]; |
||||
if (blockId === GIF_FILE.IMAGE) { // image block
|
||||
parseImg(); |
||||
if (gif.firstFrameOnly) { finnished(); return } |
||||
} else if (blockId === GIF_FILE.EOF) { finnished(); return } |
||||
else { parseExt() } |
||||
if (typeof gif.onprogress === "function") { |
||||
gif.onprogress({ bytesRead: st.pos, totalBytes: st.data.length, frame: gif.frames.length }); |
||||
} |
||||
setTimeout(parseBlock, 0); // parsing frame async so processes can get some time in.
|
||||
}; |
||||
function cancelLoad(callback) { // cancels the loading. This will cancel the load before the next frame is decoded
|
||||
if (gif.complete) { return false } |
||||
gif.cancelCallback = callback; |
||||
gif.cancel = true; |
||||
return true; |
||||
} |
||||
function error(type) { |
||||
if (typeof gif.onerror === "function") { (gif.onerror.bind(this))({ type: type, path: [this] }) } |
||||
gif.onload = gif.onerror = undefined; |
||||
gif.loading = false; |
||||
} |
||||
function doOnloadEvent() { // fire onload event if set
|
||||
gif.currentFrame = 0; |
||||
gif.nextFrameAt = gif.lastFrameAt = new Date().valueOf(); // just sets the time now
|
||||
if (typeof gif.onload === "function") { (gif.onload.bind(gif))({ type: 'load', path: [gif] }) } |
||||
gif.onerror = gif.onload = undefined; |
||||
} |
||||
function dataLoaded(data) { // Data loaded create stream and parse
|
||||
st = new Stream(data); |
||||
parse(); |
||||
} |
||||
function loadGif(filename) { // starts the load
|
||||
var ajax = new XMLHttpRequest(); |
||||
ajax.responseType = "arraybuffer"; |
||||
ajax.onload = function (e) { |
||||
if (e.target.status === 404) { error("File not found") } |
||||
else if (e.target.status >= 200 && e.target.status < 300) { dataLoaded(ajax.response) } |
||||
else { error("Loading error : " + e.target.status) } |
||||
}; |
||||
ajax.open('GET', filename, true); |
||||
ajax.send(); |
||||
ajax.onerror = function (e) { error("File error") }; |
||||
this.src = filename; |
||||
this.loading = true; |
||||
} |
||||
function play() { // starts play if paused
|
||||
if (!gif.playing) { |
||||
gif.paused = false; |
||||
gif.playing = true; |
||||
playing(); |
||||
} |
||||
} |
||||
function pause() { // stops play
|
||||
gif.paused = true; |
||||
gif.playing = false; |
||||
clearTimeout(timerID); |
||||
} |
||||
function togglePlay() { |
||||
if (gif.paused || !gif.playing) { gif.play() } |
||||
else { gif.pause() } |
||||
} |
||||
function seekFrame(frame) { // seeks to frame number.
|
||||
clearTimeout(timerID); |
||||
gif.currentFrame = frame % gif.frames.length; |
||||
if (gif.playing) { playing() } |
||||
else { gif.image = gif.frames[gif.currentFrame].image } |
||||
} |
||||
function seek(time) { // time in Seconds // seek to frame that would be displayed at time
|
||||
clearTimeout(timerID); |
||||
if (time < 0) { time = 0 } |
||||
time *= 1000; // in ms
|
||||
time %= gif.length; |
||||
var frame = 0; |
||||
while (time > gif.frames[frame].time + gif.frames[frame].delay && frame < gif.frames.length) { frame += 1 } |
||||
gif.currentFrame = frame; |
||||
if (gif.playing) { playing() } |
||||
else { gif.image = gif.frames[gif.currentFrame].image } |
||||
} |
||||
function playing() { |
||||
var delay; |
||||
var frame; |
||||
if (gif.playSpeed === 0) { |
||||
gif.pause(); |
||||
return; |
||||
} else { |
||||
if (gif.playSpeed < 0) { |
||||
gif.currentFrame -= 1; |
||||
if (gif.currentFrame < 0) { gif.currentFrame = gif.frames.length - 1 } |
||||
frame = gif.currentFrame; |
||||
frame -= 1; |
||||
if (frame < 0) { frame = gif.frames.length - 1 } |
||||
delay = -gif.frames[frame].delay * 1 / gif.playSpeed; |
||||
} else { |
||||
gif.currentFrame += 1; |
||||
gif.currentFrame %= gif.frames.length; |
||||
delay = gif.frames[gif.currentFrame].delay * 1 / gif.playSpeed; |
||||
} |
||||
gif.image = gif.frames[gif.currentFrame].image; |
||||
timerID = setTimeout(playing, delay); |
||||
} |
||||
} |
||||
var gif = { // the gif image object
|
||||
onload: null, // fire on load. Use waitTillDone = true to have load fire at end or false to fire on first frame
|
||||
onerror: null, // fires on error
|
||||
onprogress: null, // fires a load progress event
|
||||
onloadall: null, // event fires when all frames have loaded and gif is ready
|
||||
paused: false, // true if paused
|
||||
playing: false, // true if playing
|
||||
waitTillDone: true, // If true onload will fire when all frames loaded, if false, onload will fire when first frame has loaded
|
||||
loading: false, // true if still loading
|
||||
firstFrameOnly: false, // if true only load the first frame
|
||||
width: null, // width in pixels
|
||||
height: null, // height in pixels
|
||||
frames: [], // array of frames
|
||||
comment: "", // comments if found in file. Note I remember that some gifs have comments per frame if so this will be all comment concatenated
|
||||
length: 0, // gif length in ms (1/1000 second)
|
||||
currentFrame: 0, // current frame.
|
||||
frameCount: 0, // number of frames
|
||||
playSpeed: 1, // play speed 1 normal, 2 twice 0.5 half, -1 reverse etc...
|
||||
lastFrame: null, // temp hold last frame loaded so you can display the gif as it loads
|
||||
image: null, // the current image at the currentFrame
|
||||
playOnLoad: true, // if true starts playback when loaded
|
||||
// functions
|
||||
load: loadGif, // call this to load a file
|
||||
cancel: cancelLoad, // call to stop loading
|
||||
play: play, // call to start play
|
||||
pause: pause, // call to pause
|
||||
seek: seek, // call to seek to time
|
||||
seekFrame: seekFrame, // call to seek to frame
|
||||
togglePlay: togglePlay, // call to toggle play and pause state
|
||||
}; |
||||
return gif; |
||||
} |
||||
|
||||
export { |
||||
GIF |
||||
} |
@ -0,0 +1,19 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>拳皇</title> |
||||
<link rel="stylesheet" href="/static/css/base.css"> |
||||
<script src="https://cdn.acwing.com/static/jquery/js/jquery-3.3.1.min.js"></script> |
||||
</head> |
||||
<body> |
||||
<div id="kof"></div> |
||||
<script type="module"> |
||||
import {KOF} from '/static/js/base.js'; |
||||
let kof = new KOF('kof'); |
||||
|
||||
</script> |
||||
</body> |
||||
</html> |