├── .gitignore ├── .gitlab-ci.yml ├── License ├── package-lock.json ├── package.json ├── readme.md ├── sdk └── index.js ├── src ├── engine │ ├── canvas.js │ ├── clip.js │ ├── eventMixin.js │ ├── index.js │ ├── label.js │ ├── message.js │ ├── node.js │ ├── resource.js │ ├── scrollList.js │ ├── scrollView.js │ ├── sprite.js │ ├── tinyUtil.js │ ├── touch.js │ ├── touchEventMixin.js │ └── tween.js ├── main.js └── rank │ ├── cycle.js │ ├── fakeData.js │ ├── loading.js │ ├── rankLayer.js │ └── rankNode.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.history 2 | /node_modules 3 | /dist -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | echo: 2 | script: 3 | - echo "Hello World!" -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright 2018 盛庆鸿 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@babel/core": "^7.0.0-beta.51", 14 | "@babel/preset-env": "^7.0.0-beta.51", 15 | "babel-core": "^6.26.3", 16 | "babel-loader": "^8.0.0-beta.4", 17 | "babel-preset-env": "^1.7.0", 18 | "babel-preset-es2015": "^6.24.1", 19 | "webpack": "^4.12.1", 20 | "webpack-cli": "^3.0.8" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # TinyEngine 2 | 专注于微信开放域内的简易引擎(MIT License) 3 | 4 | ## Feature(RoadMap yet ) 5 | 欢迎提需求,更欢迎PR 6 | 7 | (✂️表示支持自动裁剪,只打包用到的功能) 8 | - [x] 基于节点树结构的基础渲染功能 9 | - [ ] 增加锚点概念 10 | - [x] 触摸事件处理✂️ 11 | - [x] 资源加载、缓存 12 | - [ ] Host端sdk,简化域内渲染控制和消息传递 13 | - 控件 ✂️ 14 | - [x] Label ✂️ 15 | - [x] Sprite ✂️ 16 | - [x] 支持九宫 17 | - [x] ScrollList ✂️ (暂时只支持垂直滚动) 18 | - [x] ScrollView 19 | - [ ] ~~clip~~ (暂时移除,严重性能问题,等待微信修复) 20 | - [x] 强大的动画系统(移植https://github.com/tweenjs/tween.js)✂️ 21 | - [ ] 基于yaml的UI描述文件 ✂️ 22 | - [ ] 供Egret使用的一些工具函数 23 | - [ ] .d.ts 描述文件 24 | 25 | ## Quick Start 26 | 1. 安装Nodejs 27 | 2. git clone 28 | 3. cd tiny 29 | 4. npm i --registry=https://registry.npm.taobao.org 30 | 5. src/main.js 为子域工程入口文件,根据需要进行修改 31 | 6. npm run build 32 | 7. 将dist/index.js拷贝到项目目录 33 | 34 | ## ChangeLog 35 | - v0.2.0 2018.07.04 36 | - 拆分ScrollList和ScrollView 37 | - ScrollView增加惯性支持 38 | - 暂时移除clip(微信bug) 39 | - 修复label初始化bug 40 | - 增加label baseline设置支持 41 | - 新控件:ScrollList 节点循环利用的滚动列表 42 | - v0.3.0 2018.07.04 43 | - 动画支持! 44 | 45 | ## License (MIT) 46 | Copyright 2018 盛庆鸿 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 49 | 50 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /sdk/index.js: -------------------------------------------------------------------------------- 1 | export class TinySDK{ 2 | constructor(){ 3 | this.openDataContext = wx.getOpenDataContext() 4 | } 5 | start(){ 6 | this._sendMsg("start") 7 | } 8 | stop(){ 9 | this._sendMsg("stop") 10 | } 11 | scale(x,y){ 12 | this._sendMsg("scale",{x,y}) 13 | } 14 | _sendMsg(type,data){ 15 | this.openDataContext.postMessage({ 16 | __tinyUtil__: true, 17 | type: type, 18 | data: data, 19 | }) 20 | } 21 | sendMsg(type,data){ 22 | this._sendMsg("user",{type,data}) 23 | } 24 | } -------------------------------------------------------------------------------- /src/engine/canvas.js: -------------------------------------------------------------------------------- 1 | import {Node} from "./node" 2 | export class Canvas extends Node{ 3 | constructor(init){ 4 | super(init) 5 | this._wxCanvas = wx.getSharedCanvas() 6 | this.ctx=this._wxCanvas.getContext('2d') 7 | this.width = this._wxCanvas.width 8 | this.height = this._wxCanvas.height 9 | } 10 | 11 | _renderCanvas(){ 12 | this.ctx.setTransform(this._engine.scale.x,0,0,this.engine.scale.y,0,0) 13 | this.ctx.clearRect(0,0,this.width,this.height); 14 | for(var i=0;i { 10 | this.emit("message",data) 11 | }) 12 | } 13 | } -------------------------------------------------------------------------------- /src/engine/node.js: -------------------------------------------------------------------------------- 1 | export class Node{ 2 | constructor(init){ 3 | this._init = init 4 | 5 | this._children = [] 6 | this._parent = null 7 | this.x = 0 8 | this.y = 0 9 | this.width = 0 10 | this.height = 0 11 | 12 | this._active = true 13 | this.engine = null 14 | if(this.__mixin__){ 15 | for(var i =0;i 0 && x< this.width && y > 0 && y< this.height 159 | } 160 | } -------------------------------------------------------------------------------- /src/engine/resource.js: -------------------------------------------------------------------------------- 1 | var instance = null 2 | 3 | export class ResourceLoader{ 4 | static get instance(){ 5 | if(!instance){ 6 | instance = new ResourceLoader() 7 | } 8 | return instance 9 | } 10 | 11 | constructor(){ 12 | this._cache = {} 13 | this._loading = {} 14 | } 15 | loadImgAuto(src){ 16 | if(this._cache[src]){ 17 | return this._cache[src] 18 | } 19 | var image = wx.createImage() 20 | image.src = src 21 | this._cache[src] = image 22 | return image 23 | } 24 | loadImgAsync(src){ 25 | if(this._cache[src]){ 26 | return new Promise(resolve=>resolve(this._cache[src])) 27 | } 28 | if(this._loading[src]){ 29 | return this._loading[src] 30 | } 31 | var image = wx.createImage() 32 | image.src = src 33 | var p = new Promise((resolve)=>{ 34 | image.onload = ()=>{ 35 | delete this._loading[src] 36 | this._cache[src] = image 37 | resolve(image) 38 | } 39 | }) 40 | this._loading[src] = p 41 | return p 42 | } 43 | } -------------------------------------------------------------------------------- /src/engine/scrollList.js: -------------------------------------------------------------------------------- 1 | import { ScrollView } from "./scrollView"; 2 | export class ScrollList extends ScrollView{ 3 | constructor(init){ 4 | super(init) 5 | 6 | this.createNode = null 7 | this.nodeHeight = 0 8 | this.data = [] 9 | } 10 | onEnter(){ 11 | ScrollView.prototype.onEnter.call(this) 12 | if(this.createNode == null){ 13 | throw Error("ScrollList.createNode is null") 14 | } 15 | if(this.nodeHeight == 0){ 16 | throw Error("ScrollList.nodeHeight is 0") 17 | } 18 | } 19 | onScroll(dt){ 20 | var minY = this.height 21 | var minNode = 0 22 | var maxY = 0 23 | var maxNode = 0 24 | for(var i =0;i this._children[i].y){ 26 | minY = this._children[i].y 27 | minNode = this._children[i] 28 | } 29 | if(maxY < this._children[i].y + this.nodeHeight){ 30 | maxY = this._children[i].y + this.nodeHeight 31 | maxNode = this._children[i] 32 | } 33 | } 34 | if(minY>0&&minNode._scrollListIndex>0){ 35 | maxNode._scrollListIndex = minNode._scrollListIndex-1 36 | maxNode.setData(this.data[minNode._scrollListIndex-1]) 37 | maxNode.y = minY - this.nodeHeight 38 | } 39 | if(maxY < this.height && this.data.length > maxNode._scrollListIndex+1 ){ 40 | minNode._scrollListIndex = maxNode._scrollListIndex+1 41 | minNode.setData(this.data[maxNode._scrollListIndex+1]) 42 | minNode.y = maxY 43 | } 44 | } 45 | setData(data){ 46 | this.data = data 47 | this.rebuild() 48 | } 49 | 50 | rebuild(){ 51 | this.removeAllChildren() 52 | var height = 0 53 | var maxNode = Math.ceil(this.height / this.nodeHeight)+1 54 | var nodeCount = 0 55 | while(true){ 56 | if(nodeCount >= maxNode){ 57 | break 58 | } 59 | if(nodeCount >= this.data.length){ 60 | break 61 | } 62 | var node = this.createNode() 63 | node.y = height 64 | height += this.nodeHeight 65 | node.setData && node.setData(this.data[nodeCount]) 66 | node._scrollListIndex = nodeCount 67 | this.addChild(node) 68 | nodeCount++ 69 | } 70 | this.scrollY = 0 71 | this.maxY = this.nodeHeight * this.data.length 72 | } 73 | } -------------------------------------------------------------------------------- /src/engine/scrollView.js: -------------------------------------------------------------------------------- 1 | import {Node} from "./node" 2 | export class ScrollView extends Node{ 3 | constructor(init){ 4 | super(init) 5 | this.maxY = 0 6 | this.scrollY = 0 7 | this.speed = 0 8 | this._last5move = [] 9 | this._last5time = [] 10 | this.brake=0.2 11 | } 12 | onEnter(){ 13 | this.calculate() 14 | } 15 | onAddChild(){ 16 | this.calculate() 17 | } 18 | onRemoveChild(){ 19 | this.calculate() 20 | } 21 | calculate(){ 22 | this.maxY = 0 23 | for(var i =0;i= 0){ 36 | dy = -this.scrollY 37 | this.speed = 0 38 | } 39 | if(this.scrollY + dy < -this.maxY + this.height){ 40 | dy = -(this.scrollY + this.maxY - this.height) 41 | this.speed = 0 42 | } 43 | 44 | if(dy == 0){ 45 | return 46 | } 47 | 48 | this.scrollY += dy 49 | 50 | for(var i = 0;i 0){ 59 | this.speed-=dt*0.001 60 | if(this.speed<0)this.speed =0 61 | }else{ 62 | this.speed+=dt*0.001 63 | if(this.speed>0)this.speed =0 64 | } 65 | 66 | if(this.speed<0.03 && this.speed>-0.03){ 67 | this.speed = 0 68 | } 69 | } 70 | onTouchBegin(touch){ 71 | console.log("onTouchBegin1",touch.current.x,touch.current.y) 72 | if(this.maxY <= this.height){ 73 | return 74 | } 75 | var local = this.globalToLocal(touch.current.x,touch.current.y) 76 | if(!this.isInside(local.x,local.y)){ 77 | return 78 | } 79 | console.log("onTouchBegin2") 80 | this.speed =0 81 | this._last5move = [] 82 | return true 83 | } 84 | onTouchMove(touch){ 85 | var dy = touch.delta.y 86 | 87 | if(this.scrollY + dy >= 0){ 88 | dy = -this.scrollY 89 | } 90 | if(this.scrollY + dy < -this.maxY + this.height){ 91 | dy = -(this.scrollY + this.maxY - this.height) 92 | } 93 | 94 | if(dy == 0){ 95 | return 96 | } 97 | 98 | this.scrollY += dy 99 | 100 | for(var i = 0;i20){ 104 | touch.swallow = true 105 | } 106 | if(this.onScroll){ 107 | this.onScroll(dy) 108 | } 109 | 110 | this._gatherTouchMove(dy,touch.interval) 111 | console.log(this.scrollY,this.maxY,dy) 112 | } 113 | onTouchEnd(touch){ 114 | if(this.scrollY>= 0){ 115 | this.speed =0 116 | return 117 | } 118 | if(this.scrollY <= -this.maxY){ 119 | this.speed=0 120 | return 121 | } 122 | this._gatherTouchMove(touch.delta.y,touch.interval) 123 | var totalTime = 0 124 | var totalMovement = 0 125 | for(var i =0;i500){ 130 | this.speed=0 131 | return 132 | } 133 | 134 | this.speed = totalMovement * (1-this.brake)/totalTime 135 | console.log("speed",this.speed) 136 | } 137 | onTouchCancel(touch){ 138 | 139 | } 140 | _gatherTouchMove(dy,dt){ 141 | this._last5move.push(dy) 142 | this._last5time.push(dt) 143 | if(this._last5move.length>5){ 144 | this._last5move.shift() 145 | this._last5time.shift() 146 | } 147 | } 148 | onEnter(){ 149 | this.engine.touchManager.addLayer(10,this) 150 | } 151 | onExit(){ 152 | this.engine.touchManager.removeLayer(this) 153 | } 154 | _render(ctx,a,b,c,d,e,f){ 155 | var canvasObj = this.engine._getTempCanvas(this.width,this.height) 156 | canvasObj.context.globalCompositeOperation = "source-over" 157 | canvasObj.context.setTransform(1,0,0,1,0,0) 158 | canvasObj.context.clearRect(0,0,this.width,this.height) 159 | 160 | for(var i=0;i{ 42 | this.image = image 43 | if(autoFitImgSize){ 44 | this.fitImgSize() 45 | } 46 | }) 47 | } 48 | } -------------------------------------------------------------------------------- /src/engine/tinyUtil.js: -------------------------------------------------------------------------------- 1 | import { MessageManager } from "./message"; 2 | import { EventMixin } from "./eventMixin"; 3 | 4 | export class TinyUtil extends EventMixin{ 5 | static addon(engine){ 6 | if(!engine.messageManager){ 7 | MessageManager.addon(engine) 8 | } 9 | engine.tineUtil = engine.tineUtil || new TinyUtil(engine) 10 | } 11 | constructor(engine){ 12 | this._engine = engine 13 | engine.messageManager.on("message",this._onMessagge.bind(this)) 14 | } 15 | _onMessagge(msg){ 16 | if(!msg || !msg.__tinyUtil__){ 17 | this.emit("illegal",msg) 18 | return 19 | } 20 | switch(msg.type){ 21 | case "start": 22 | this._engine.start() 23 | break 24 | case "stop": 25 | this._engine.stop() 26 | break 27 | case "scale": 28 | this._engine.scale(msg.data.x,msg.data.y) 29 | case "user": 30 | this.emit("msg",msg.data) 31 | this.emit(data.type,data.data) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/engine/touch.js: -------------------------------------------------------------------------------- 1 | 2 | export class Touch{ 3 | constructor(id,x,y){ 4 | this.id = id 5 | this.begin = {x, y} 6 | this.beginAt = Date.now() 7 | this.current = {x, y} 8 | this.currentAt = this.beginAt 9 | this.delta = {x:0,y:0} 10 | this.interval = 0 11 | this.inviter = [] 12 | this.handle = false 13 | this.swallow = false 14 | } 15 | 16 | _moveTo(x,y){ 17 | if(x == this.current.x && y == this.current.y)return 18 | var last = this.current 19 | var lastAt = this.currentAt 20 | this.current = {x,y} 21 | this.currentAt = Date.now() 22 | this.delta = { 23 | x: this.current.x - last.x, 24 | y: this.current.y - last.y, 25 | } 26 | this.interval = this.currentAt - lastAt 27 | } 28 | 29 | get distance(){ 30 | var dx = this.current.x - this.begin.x 31 | var dy = this.current.y - this.begin.y 32 | return Math.sqrt(dx*dx+dy*dy) 33 | } 34 | } 35 | 36 | export class TouchManager{ 37 | static addon(engine){ 38 | engine.touchManager = new TouchManager(engine) 39 | } 40 | 41 | constructor(engine){ 42 | this.engine = engine 43 | this.touch = null 44 | this.handlers = { 45 | begin:this._onTouchBegin.bind(this), 46 | move: this._onTouchMove.bind(this), 47 | end:this._onTouchEnd.bind(this), 48 | cancel:this._onTouchCancel.bind(this) 49 | } 50 | this.layers = [] 51 | this.layerNodes = {} 52 | this.layerNodeMap = new Map() 53 | this._addListener() 54 | } 55 | 56 | addLayer(layerIndex,node){ 57 | if(this.layers.indexOf(layerIndex) == -1){ 58 | this.layers.push(layerIndex) 59 | this.layers.sort() 60 | this.layerNodes[layerIndex.toString()] = [] 61 | } 62 | this.layerNodes[layerIndex.toString()].push(node) 63 | this.layerNodeMap.set(node,layerIndex) 64 | } 65 | 66 | removeLayer(node){ 67 | var layerIndex = this.layerNodeMap.get(node) 68 | if(layerIndex === undefined){ 69 | return 70 | } 71 | this.layerNodeMap.delete(node) 72 | var layer = this.layerNodes[layerIndex.toString()] 73 | if(!layer){ 74 | return 75 | } 76 | var index = layer.indexOf(node) 77 | if(index>=0){ 78 | layer.splice(index,1) 79 | } 80 | } 81 | 82 | _addListener(){ 83 | wx.onTouchStart(this.handlers.begin) 84 | wx.onTouchMove(this.handlers.move) 85 | wx.onTouchEnd(this.handlers.end) 86 | wx.onTouchCancel(this.handlers.cancel) 87 | } 88 | 89 | _removeListener(){ 90 | wx.offTouchStart(this.handlers.begin) 91 | wx.offTouchMove(this.handlers.move) 92 | wx.offTouchEnd(this.handlers.end) 93 | wx.offTouchCancel(this.handlers.cancel) 94 | } 95 | 96 | _onTouchBegin(args){ 97 | if(!this.engine.updateTimer){ 98 | return 99 | } 100 | if(this.touch != null){ 101 | return 102 | } 103 | if(args.changedTouches.length <= 0){ 104 | return 105 | } 106 | 107 | var raw = args.changedTouches[0] 108 | 109 | this.touch = new Touch(raw.identifier,raw.pageX*this.engine.scale.x,raw.pageY*this.engine.scale.y) 110 | 111 | for(var i = 0;i{ 18 | this.engine.touchManager.addLayer(this.touchLayer,this) 19 | }) 20 | 21 | this.on("exit",()=>{ 22 | this.engine.touchManager.removeLayer(this) 23 | }) 24 | } 25 | 26 | TouchEventMixin.prototype.onTouchBegin = function(touch){ 27 | var local = this.globalToLocal(touch.current.x,touch.current.y) 28 | if(this.isInside(local.x,local.y)){ 29 | this.emit("touchBegin",touch,local) 30 | return true 31 | } 32 | } 33 | TouchEventMixin.prototype.onTouchMove = function(touch){ 34 | this.emit("touchMove",touch) 35 | } 36 | TouchEventMixin.prototype.onTouchEnd = function(touch){ 37 | this.emit("touchEnd",touch) 38 | } 39 | TouchEventMixin.prototype.onTouchCancel = function(touch){ 40 | this.emit("touchCancel",touch) 41 | } 42 | TouchEventMixin.prototype.onTouchTap = function(touch){ 43 | this.emit("touchTap",touch) 44 | } -------------------------------------------------------------------------------- /src/engine/tween.js: -------------------------------------------------------------------------------- 1 | export class TweenGroup{ 2 | constructor(){ 3 | this._tweens = new Map() 4 | } 5 | removeAll(){ 6 | this._tweens = new Map() 7 | } 8 | add(tween){ 9 | this._tweens.set(tween.id,tween) 10 | } 11 | remove(tween){ 12 | this._tweens.delete(tween.id) 13 | } 14 | update(dt){ 15 | for(var tween of this._tweens.values()){ 16 | if(tween.update && tween.update(dt) === false){ 17 | tween.isPlaying = false 18 | this._tweens.delete(tween.id) 19 | } 20 | } 21 | } 22 | } 23 | 24 | var defaultGroup = new TweenGroup() 25 | 26 | var id = 0 27 | function nextId(){ 28 | return id++ 29 | } 30 | 31 | export class Tween{ 32 | static addon(engine){ 33 | engine.on("update",defaultGroup.update.bind(defaultGroup)) 34 | } 35 | 36 | constructor(target,group){ 37 | this.id = nextId() 38 | this._target = target 39 | this._group = group || defaultGroup 40 | 41 | this._valuesStart = {} 42 | this._valuesEnd = {} 43 | this._valuesStartRepeat = {} 44 | 45 | this._duration = 1000 46 | this._repeat = 0 47 | this._repeatDelayTime = undefined 48 | this._isPlaying = false 49 | this._reversed = false 50 | this._delayTime = 0 51 | this._startTime = null 52 | this._easingFunction = TweenEasing.Linear.None 53 | this._interpolation = TweenInterpolation.Linear 54 | this._chainedTweens = [] 55 | this._onStart = null 56 | this._onStartFired = false 57 | this._onUpdate = null 58 | this._onComplete = null 59 | this._onStop = null 60 | } 61 | 62 | isPlaying(){ 63 | return this._isPlaying 64 | } 65 | 66 | to(props,duration){ 67 | this._valuesEnd = props 68 | if(duration!==undefined){ 69 | this._duration = duration 70 | } 71 | return this 72 | } 73 | 74 | start(delay){ 75 | this._group.add(this) 76 | this._isPlaying = true 77 | this._onStartFired = false 78 | this._startTime = delay || 0 79 | this._startTime+=this._delayTime 80 | 81 | for(var key in this._valuesEnd){ 82 | if(this._valuesEnd[key] instanceof Array){ 83 | if(this._valuesEnd[key].length ==0){ 84 | continue 85 | } 86 | 87 | this._valuesEnd[key] = [this._target[key]].concat(this._valuesEnd[key]) 88 | } 89 | 90 | if(this._target[key]===undefined){ 91 | continue 92 | } 93 | 94 | this._valuesStart[key] = this.target[key] 95 | if(!this._valuesStart[key] instanceof Array){ 96 | this._valuesStart[key] *= 1.0 97 | } 98 | this._valuesStartRepeat[key] = this._valuesStart[key] || 0 99 | } 100 | 101 | return this 102 | } 103 | 104 | stop(){ 105 | if(!this._isPlaying){ 106 | return this 107 | } 108 | this._group.remove(this) 109 | this._isPlaying = false 110 | if(this._onStop){ 111 | this._onStop(this._target) 112 | } 113 | this.stopChainedTweens() 114 | return this 115 | } 116 | 117 | end(){ 118 | this.update(this._startTime + this._duration) 119 | return this 120 | } 121 | 122 | stopChainedTweens(){ 123 | for(var i =0;i0){ 177 | return true 178 | } 179 | 180 | if(this._onStartFired == false){ 181 | this._onStart && this._onStart(this._target) 182 | this._onStartFired = true 183 | } 184 | 185 | var elapsed = this._startTime / this._duration 186 | elapsed = (this._duration == 0 || elapsed > 1)?1:elapsed 187 | 188 | var value = this._easingFunction(elapsed) 189 | 190 | for(var key in this._valuesEnd){ 191 | if(this._valuesStart[key]===undefined){ 192 | continue 193 | } 194 | var start = this._valuesStart[key] || 0 195 | var end = this._valuesEnd[key] || 0 196 | if(end instanceof Array){ 197 | this._target[key] = this._interpolation(end,value) 198 | }else{ 199 | if( typeof(end)==="string"){ 200 | if(end.charAt(0)==='+' || end.charAt(0)==='-'){ 201 | end = start + parseFloat(end) 202 | }else{ 203 | end = parseFloat(end) 204 | } 205 | } 206 | if(typeof(end)==="number"){ 207 | this._target[key] = start + (end - start) * value 208 | } 209 | } 210 | } 211 | 212 | if(this._onUpdate){ 213 | this._onUpdate(this._target) 214 | } 215 | 216 | if(elapsed == 1){ 217 | if(this._repeat>0){ 218 | if(isFinite(this._repeat)){ 219 | this._repeat-- 220 | } 221 | for(var key in this._valuesStartRepeat){ 222 | if(typeof(this._valuesEnd[key])==="string"){ 223 | this._valuesStartRepeat[key] = this._valuesStart[key] + parseFloat(this._valuesEnd[key]) 224 | } 225 | this._valuesStart[key]=this._valuesStartRepeat[key] 226 | } 227 | if(this._repeatDelayTime!==undefined){ 228 | this._startTime = this._repeatDelayTime 229 | } 230 | return true 231 | }else{ 232 | if(this._onComplete){ 233 | this._onComplete(this._target) 234 | } 235 | for(var i =0;i 1) { 407 | return fn(v[m], v[m - 1], m - f); 408 | } 409 | 410 | return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); 411 | 412 | }, 413 | 414 | Bezier: function (v, k) { 415 | 416 | var b = 0; 417 | var n = v.length - 1; 418 | var pw = Math.pow; 419 | var bn = TweenInterpolation.Utils.Bernstein; 420 | 421 | for (var i = 0; i <= n; i++) { 422 | b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); 423 | } 424 | 425 | return b; 426 | 427 | }, 428 | 429 | CatmullRom: function (v, k) { 430 | 431 | var m = v.length - 1; 432 | var f = m * k; 433 | var i = Math.floor(f); 434 | var fn = TweenInterpolation.Utils.CatmullRom; 435 | 436 | if (v[0] === v[m]) { 437 | 438 | if (k < 0) { 439 | i = Math.floor(f = m * (1 + k)); 440 | } 441 | 442 | return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); 443 | 444 | } else { 445 | 446 | if (k < 0) { 447 | return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); 448 | } 449 | 450 | if (k > 1) { 451 | return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); 452 | } 453 | 454 | return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); 455 | 456 | } 457 | 458 | }, 459 | 460 | Utils: { 461 | 462 | Linear: function (p0, p1, t) { 463 | 464 | return (p1 - p0) * t + p0; 465 | 466 | }, 467 | 468 | Bernstein: function (n, i) { 469 | 470 | var fc = TweenInterpolation.Utils.Factorial; 471 | 472 | return fc(n) / fc(i) / fc(n - i); 473 | 474 | }, 475 | 476 | Factorial: (function () { 477 | 478 | var a = [1]; 479 | 480 | return function (n) { 481 | 482 | var s = 1; 483 | 484 | if (a[n]) { 485 | return a[n]; 486 | } 487 | 488 | for (var i = n; i > 1; i--) { 489 | s *= i; 490 | } 491 | 492 | a[n] = s; 493 | return s; 494 | 495 | }; 496 | 497 | })(), 498 | 499 | CatmullRom: function (p0, p1, p2, p3, t) { 500 | 501 | var v0 = (p2 - p0) * 0.5; 502 | var v1 = (p3 - p1) * 0.5; 503 | var t2 = t * t; 504 | var t3 = t * t2; 505 | 506 | return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; 507 | 508 | } 509 | 510 | } 511 | 512 | }; 513 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | Label, 3 | TouchManager, 4 | Tween, 5 | Engine, 6 | } from "./engine" 7 | import { RankLayer } from "./rank/rankLayer"; 8 | 9 | var engine = new Engine() 10 | TouchManager.addon(engine) 11 | Tween.addon(engine) 12 | 13 | engine.start() 14 | 15 | var rankLayer = new RankLayer({ 16 | width:engine.canvas.width, 17 | height:engine.canvas.height, 18 | }) 19 | engine.canvas.addChild(rankLayer) 20 | 21 | var label = new Label({text:"hello world!"}) 22 | label.x = 100 23 | label.y = 100 24 | engine.canvas.addChild(label) 25 | 26 | //touch all modules for build size 27 | // console.log( 28 | // Label, 29 | // Node, 30 | // Sprite, 31 | // Touch, 32 | // TouchManager, 33 | // EventMixin, 34 | // TouchEventMixin, 35 | // ScrollList 36 | // ) 37 | 38 | // setInterval(function(){ 39 | // label.text = label.text == "Hello World!" ? "" :"Hello World!" 40 | // },500) -------------------------------------------------------------------------------- /src/rank/cycle.js: -------------------------------------------------------------------------------- 1 | import {Node} from "../engine" 2 | export class Cycle extends Node{ 3 | constructor(init){ 4 | super(init) 5 | this.r = this.r||0 6 | } 7 | render(ctx,a,b,c,d,e,f){ 8 | ctx.fillStyle = "#000000" 9 | ctx.beginPath(); 10 | ctx.arc(this.width/2,this.height/2,this.r,0,2*Math.PI) 11 | ctx.fill() 12 | // ctx.fillRect(0,0,15,15) 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/rank/fakeData.js: -------------------------------------------------------------------------------- 1 | var rankData = [] 2 | 3 | for(var i =0;i<100;i++){ 4 | rankData.push({ 5 | name:"玩家"+i, 6 | level: Math.floor(Math.random() * 99) + 1, 7 | gold: Math.floor(Math.random() * 100000), 8 | score: Math.floor(Math.random() * 100000), 9 | img:"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqo7QFp65LC0jF8BKibX2YMXv9PErsOibVSkgua1iaM00BejdzTI8Ysj51II5upicZQIVGiaXVYsMKicA8Q/132", 10 | }) 11 | } 12 | 13 | export function getRankData(){ 14 | return new Promise(resolve=>{ 15 | setTimeout(()=>{ 16 | resolve(rankData) 17 | },2000) 18 | }) 19 | } -------------------------------------------------------------------------------- /src/rank/loading.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc122333/TinyEngine/366bb7ab53d1ca6212e31b5541004e2606b5e15f/src/rank/loading.js -------------------------------------------------------------------------------- /src/rank/rankLayer.js: -------------------------------------------------------------------------------- 1 | import { 2 | Label, 3 | Node, 4 | Sprite, 5 | Touch, 6 | TouchManager, 7 | EventMixin, 8 | TouchEventMixin, 9 | ResourceLoader, 10 | ScrollList 11 | } from "../engine" 12 | import { getRankData } from "./fakeData"; 13 | import { RankNode } from "./rankNode"; 14 | 15 | export class RankLayer extends Node{ 16 | constructor(init){ 17 | super(init) 18 | 19 | { 20 | var tabs = new Node() 21 | tabs.x = 0 22 | tabs.width = this.width 23 | tabs.height = 100 24 | 25 | { 26 | let tab1Button = new Sprite() 27 | tab1Button.wait(ResourceLoader.instance.loadImgAsync("images/main_btn0.png"),false) 28 | tab1Button.patch9 = {x:25,y:16,w:10,h:5} 29 | tab1Button.width = this.width / 3 30 | tab1Button.height = 50 31 | tab1Button.x = 0 32 | tab1Button.y = 50 33 | tab1Button.mixin(TouchEventMixin) 34 | tab1Button.on("touchTap",this.switchTabGoldRank.bind(this)) 35 | let label = new Label({text:"金币"}) 36 | label.x = 10; 37 | label.y = 10; 38 | tab1Button.addChild(label) 39 | tabs.addChild(tab1Button) 40 | } 41 | { 42 | let tab2Button = new Sprite() 43 | tab2Button.wait(ResourceLoader.instance.loadImgAsync("images/main_btn0.png"),false) 44 | tab2Button.patch9 = {x:25,y:16,w:10,h:5} 45 | tab2Button.width = this.width / 3 46 | tab2Button.height = 50 47 | tab2Button.x = this.width / 3 48 | tab2Button.y = 50 49 | tab2Button.mixin(TouchEventMixin) 50 | tab2Button.on("touchTap",this.switchTabLevelRank.bind(this)) 51 | let label = new Label({text:"等级"}) 52 | label.x = 10; 53 | label.y = 10; 54 | tab2Button.addChild(label) 55 | tabs.addChild(tab2Button) 56 | } 57 | { 58 | let tab3Button = new Sprite() 59 | tab3Button.wait(ResourceLoader.instance.loadImgAsync("images/main_btn0.png"),false) 60 | tab3Button.patch9 = {x:25,y:16,w:10,h:5} 61 | tab3Button.width = this.width / 3 62 | tab3Button.height = 50 63 | tab3Button.x = 2 * this.width / 3 64 | tab3Button.y = 50 65 | tab3Button.mixin(TouchEventMixin) 66 | tab3Button.on("touchTap",this.switchTabScoreRank.bind(this)) 67 | let label = new Label({text:"战力"}) 68 | label.x = 10; 69 | label.y = 10; 70 | tab3Button.addChild(label) 71 | tabs.addChild(tab3Button) 72 | } 73 | 74 | this.addChild(tabs) 75 | } 76 | 77 | var list = new ScrollList() 78 | list.width = this.width 79 | list.height = this.height - 100 80 | list.y = 100 81 | list.createNode = ()=>{ 82 | return new RankNode({type:"gold"}) 83 | } 84 | list.nodeHeight = 150 85 | list.data = this.data 86 | this.addChild(list) 87 | this.list = list 88 | } 89 | 90 | onEnter(){ 91 | getRankData().then((data)=>{ 92 | this.data = data 93 | this.switchTabGoldRank() 94 | }) 95 | } 96 | 97 | onExit(){ 98 | 99 | } 100 | switchTabGoldRank(){ 101 | console.log("switchTabGoldRank") 102 | this.list.setData(this.data) 103 | } 104 | switchTabLevelRank(){ 105 | console.log("switchTabLevelRank") 106 | this.list.setData(this.data) 107 | } 108 | switchTabScoreRank(){ 109 | console.log("switchTabScoreRank") 110 | this.list.setData(this.data) 111 | } 112 | } -------------------------------------------------------------------------------- /src/rank/rankNode.js: -------------------------------------------------------------------------------- 1 | import { Node, Label, Sprite, ResourceLoader } from "../engine"; 2 | import { Clip } from "../engine/clip"; 3 | import { Cycle } from "./cycle"; 4 | 5 | export class RankNode extends Node{ 6 | constructor(init){ 7 | super(init) 8 | this.height = 100 9 | { 10 | this.head = new Sprite({width:132,height:132}) 11 | this.addChild(this.head) 12 | } 13 | { 14 | this.label = new Label({text:""}) 15 | this.addChild(this.label) 16 | } 17 | 18 | } 19 | setData(data){ 20 | console.log("setData",data.name) 21 | this.data = data 22 | this.label.text = data.name 23 | this.head.wait(ResourceLoader.instance.loadImgAsync(this.data.img),false) 24 | } 25 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/main.js', 6 | devtool: "source-map", 7 | output: { 8 | filename: 'index.js', 9 | path: path.resolve(__dirname, 'dist') 10 | } 11 | }; --------------------------------------------------------------------------------