diff --git a/static/css/base.css b/static/css/base.css index e69de29..d6908a3 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -0,0 +1,7 @@ +#kof { + width: 960px; + height: 540px; + background-image: url('/static/images/background/0.gif'); + background-size: 200% 100%; + background-position: top; +} \ No newline at end of file diff --git a/static/images/background/0.gif b/static/images/background/0.gif new file mode 100644 index 0000000..3d6a69b Binary files /dev/null and b/static/images/background/0.gif differ diff --git a/static/images/player/kyo/0.gif b/static/images/player/kyo/0.gif new file mode 100644 index 0000000..e784f31 Binary files /dev/null and b/static/images/player/kyo/0.gif differ diff --git a/static/images/player/kyo/1.gif b/static/images/player/kyo/1.gif new file mode 100644 index 0000000..28dba86 Binary files /dev/null and b/static/images/player/kyo/1.gif differ diff --git a/static/images/player/kyo/2.gif b/static/images/player/kyo/2.gif new file mode 100644 index 0000000..bf8d71b Binary files /dev/null and b/static/images/player/kyo/2.gif differ diff --git a/static/images/player/kyo/3.gif b/static/images/player/kyo/3.gif new file mode 100644 index 0000000..dd1e506 Binary files /dev/null and b/static/images/player/kyo/3.gif differ diff --git a/static/images/player/kyo/4.gif b/static/images/player/kyo/4.gif new file mode 100644 index 0000000..d2cad44 Binary files /dev/null and b/static/images/player/kyo/4.gif differ diff --git a/static/images/player/kyo/5.gif b/static/images/player/kyo/5.gif new file mode 100644 index 0000000..251d570 Binary files /dev/null and b/static/images/player/kyo/5.gif differ diff --git a/static/images/player/kyo/6.gif b/static/images/player/kyo/6.gif new file mode 100644 index 0000000..98fb67d Binary files /dev/null and b/static/images/player/kyo/6.gif differ diff --git a/static/js/ac_game_object/base.js b/static/js/ac_game_object/base.js new file mode 100644 index 0000000..6defaa6 --- /dev/null +++ b/static/js/ac_game_object/base.js @@ -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, +} \ No newline at end of file diff --git a/static/js/base.js b/static/js/base.js index e69de29..1ccbd73 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -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, +} \ No newline at end of file diff --git a/static/js/controller/base.js b/static/js/controller/base.js new file mode 100644 index 0000000..da588ba --- /dev/null +++ b/static/js/controller/base.js @@ -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); + }); + } +} \ No newline at end of file diff --git a/static/js/game_map/base.js b/static/js/game_map/base.js new file mode 100644 index 0000000..96e62da --- /dev/null +++ b/static/js/game_map/base.js @@ -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 = $(''); + 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, +} \ No newline at end of file diff --git a/static/js/player/base.js b/static/js/player/base.js new file mode 100644 index 0000000..c663666 --- /dev/null +++ b/static/js/player/base.js @@ -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++; + } +} + diff --git a/static/js/player/kyo.js b/static/js/player/kyo.js new file mode 100644 index 0000000..9eb1b46 --- /dev/null +++ b/static/js/player/kyo.js @@ -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; + } + }; + } + } +} \ No newline at end of file diff --git a/static/js/utils/gif.js b/static/js/utils/gif.js new file mode 100644 index 0000000..f2582c2 --- /dev/null +++ b/static/js/utils/gif.js @@ -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 +} diff --git a/template/index.html b/template/index.html index e69de29..3bebbe2 100644 --- a/template/index.html +++ b/template/index.html @@ -0,0 +1,19 @@ + + + + + + + 拳皇 + + + + +
+ + + \ No newline at end of file