首页 > 编程语言 >JavaScript 一些实用辅助类库

JavaScript 一些实用辅助类库

时间:2022-09-02 23:14:23浏览次数:61  
标签:类库 JavaScript return .# len 实用 ._ line null

"use strict";

var __emptyPoint = null,

__emptyContext = null;

const ColorRefTable = [
    ['aliceblue','#f0f8ff'], ['antiquewhite','#faebd7'], ['aqua','#00ffff'],
    ['aquamarine','#7fffd4'], ['azure','#f0ffff'], ['beige','#f5f5dc'],
    ['bisque','#ffe4c4'], ['black','#000000'], ['blanchedalmond','#ffebcd'],
    ['blue','#0000ff'], ['blueviolet','#8a2be2'], ['brown','#a52a2a'],
    ['burlywood','#deb887'], ['cadetblue','#5f9ea0'], ['chartreuse','#7fff00'],
    ['chocolate','#d2691e'], ['coral','#ff7f50'], ['cornflowerblue'],
    ['cornsilk','#fff8dc'], ['crimson','#dc143c'], ['cyan','#00ffff'],
    ['darkblue','#00008b'], ['darkcyan','#008b8b'], ['darkgoldenrod','#b8860b'],
    ['darkgray','#a9a9a9'], ['darkgreen','#006400'], ['darkgrey','#a9a9a9'],
    ['darkkhaki','#bdb76b'], ['darkmagenta','#8b008b'], ['firebrick','#b22222'],
    ['darkolivegreen','#556b2f'], ['darkorange','#ff8c00'], ['darkorchid','#9932cc'],
    ['darkred','#8b0000'], ['darksalmon','#e9967a'], ['darkseagreen','#8fbc8f'], 
    ['darkslateblue','#483d8b'], ['darkslategray','#2f4f4f'], ['darkslategrey','#2f4f4f'],
    ['darkturquoise','#00ced1'], ['darkviolet','#9400d3'], ['deeppink','#ff1493'],
    ['deepskyblue','#00bfff'], ['dimgray','#696969'], ['dimgrey','#696969'],
    ['dodgerblue','#1e90ff'], ['floralwhite','#fffaf0'], ['forestgreen','#228b22'],
    ['fuchsia','#ff00ff'], ['gainsboro','#dcdcdc'], ['ghostwhite','#f8f8ff'],
    ['gold','#ffd700'], ['goldenrod','#daa520'], ['gray','#808080'],
    ['green','#008000'], ['greenyellow','#adff2f'], ['grey','#808080'],
    ['honeydew','#f0fff0'], ['hotpink','#ff69b4'], ['indianred','#cd5c5c'],
    ['indigo','#4b0082'], ['ivory','#fffff0'], ['khaki','#f0e68c'],
    ['lavender','#e6e6fa'], ['lavenderblush','#fff0f5'], ['lawngreen','#7cfc00'],
    ['lemonchiffon','#fffacd'], ['lightblue','#add8e6'], ['lightcoral','#f08080'],
    ['lightcyan','#e0ffff'], ['lightgoldenrodyellow','#fafad2'], ['lightgray','#d3d3d3'],
    ['lightgreen','#90ee90'], ['lightgrey','#d3d3d3'], ['lightpink','#ffb6c1'],
    ['lightsalmon','#ffa07a'], ['lightseagreen','#20b2aa'], ['lightskyblue','#87cefa'],
    ['lightslategray','#778899'], ['lightslategrey','#778899'], ['lightsteelblue','#b0c4de'],
    ['lightyellow','#ffffe0'], ['lime','#00ff00'], ['limegreen','#32cd32'],
    ['linen','#faf0e6'], ['magenta','#ff00ff'], ['maroon','#800000'],
    ['mediumaquamarine','#66cdaa'], ['mediumblue','#0000cd'], ['mediumorchid','#ba55d3'],
    ['mediumpurple','#9370db'], ['mediumseagreen','#3cb371'], ['mediumslateblue','#7b68ee'],
    ['mediumspringgreen','#00fa9a'], ['mediumturquoise','#48d1cc'], ['mediumvioletred','#c71585'],
    ['midnightblue','#191970'], ['mintcream','#f5fffa'], ['mistyrose','#ffe4e1'],
    ['moccasin','#ffe4b5'], ['navajowhite','#ffdead'], ['navy','#000080'],
    ['oldlace','#fdf5e6'], ['olive','#808000'], ['olivedrab','#6b8e23'],
    ['orange','#ffa500'], ['orangered','#ff4500'], ['orchid','#da70d6'],
    ['palegoldenrod','#eee8aa'], ['palegreen','#98fb98'], ['paleturquoise','#afeeee'],
    ['palevioletred','#db7093'], ['papayawhip','#ffefd5'], ['peachpuff','#ffdab9'],
    ['peru','#cd853f'], ['pink','#ffc0cb'], ['plum','#dda0dd'],
    ['powderblue','#b0e0e6'], ['purple','#800080'], ['red','#ff0000'],
    ['rosybrown','#bc8f8f'], ['royalblue','#4169e1'], ['saddlebrown','#8b4513'],
    ['salmon','#fa8072'], ['sandybrown','#f4a460'], ['seagreen','#2e8b57'],
    ['seashell','#fff5ee'], ['sienna','#a0522d'], ['silver','#c0c0c0'],
    ['skyblue','#87ceeb'], ['slateblue','#6a5acd'], ['slategray','#708090'],
    ['slategrey','#708090'], ['snow','#fffafa'], ['springgreen','#00ff7f'],
    ['steelblue','#4682b4'], ['tan','#d2b48c'], ['teal','#008080'],
    ['thistle','#d8bfd8'], ['tomato','#ff6347'], ['turquoise','#40e0d0'],
    ['violet','#ee82ee'], ['wheat','#f5deb3'], ['white','#ffffff'],
    ['whitesmoke','#f5f5f5'], ['yellow','#ffff00'], ['yellowgreen','#9acd32']
],

UTILS = {

    toAngle(v){
        return v / 180 * Math.PI;
    },

    emptyArray(arr){
        return !Array.isArray(arr) || arr.length === 0;
    },

    isObject(obj){
        
        return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
        
    },
    
    isNumber(num){

        return typeof num === "number" && isNaN(num) === false;

    },

    //获取最后一个点后面的字符
    getFileType(string){
        let type = "", str = string.split('').reverse().join('');
        for(let k = 0, len = str.length; k < len; k++){
            if(str[k] === ".") break;
            type += str[k];
        }
        return type.split('').reverse().join('');
    },

    //删除 string 所有的空格
    deleteSpaceAll(str){
        const len = str.length;
        var result = '';
        for(let i = 0; i < len; i++){
            if(str[i] !== '') result += str[i]
        }

        return result
    },

    //删除 string 两边空格
    removeSpaceSides(string){

        return string.replace(/(^\s*)|(\s*$)/g, "");

    },

    //返回 num 与 num1 之间的随机数
    random(num, num1){
        
        if(num < num1) return Math.random() * (num1 - num) + num;

        else if(num > num1) return Math.random() * (num - num1) + num1;

        else return num;
        
    },

    //生成 UUID
    generateUUID: function (){
        const _lut = [];
    
        for ( let i = 0; i < 256; i ++ ) {
    
            _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
    
        }
    
        return function (){
            const d0 = Math.random() * 0xffffffff | 0;
            const d1 = Math.random() * 0xffffffff | 0;
            const d2 = Math.random() * 0xffffffff | 0;
            const d3 = Math.random() * 0xffffffff | 0;
            const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
            _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
            _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
            _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
    
            return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间
        }
    }(),

    //欧几里得距离(两点的直线距离)
    distance(x, y, x1, y1){
        
        return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));

    },

    downloadFile(blob, fileName){
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
    },

    loadFileJSON(callback){
        const input = document.createElement("input");
        input.type = "file";
        input.accept = ".json";
        
        input.onchange = a => {
            if(a.target.files.length === 0) return;
            const fr = new FileReader();
            fr.onloadend = b => callback(b.target.result);
            fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
        }
        
        input.click();
    },

    get emptyPoint(){
        if(__emptyPoint === null) __emptyPoint = new Point();
        return __emptyPoint;
    },

    get emptyContext(){
        if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d')
        return __emptyContext;
    },

}




/** Ajax
parameter:
    option = {
        url:        可选, 默认 ''
        method:        可选, post 或 get请求, 默认 post
        asy:        可选, 是否异步执行, 默认 true
        success:    可选, 成功回调, 默认 null
        error:        可选, 超时或失败调用, 默认 null
        change:        可选, 请求状态改变时调用, 默认 null
        data:        可选, 如果定义则在初始化时自动执行.send(data)方法
    }

demo:
    const data = `email=${email}&password=${password}`,

    //默认 post 请求:
    ajax = new Ajax({
        url: './login',
        data: data,
        success: mes => console.log(mes),
    });
    
    //get 请求:
    ajax.method = "get";
    ajax.send(data);
*/
class Ajax{
    
    constructor(option = {}){
        this.url = option.url || "";
        this.method = option.method || "post";
        this.asy = typeof option.asy === "boolean" ? option.asy : true;
        this.success = option.success || null;
        this.error = option.error || null;
        this.change = option.change || null;

        //init XML
        this.xhr = new XMLHttpRequest();

        this.xhr.onerror = this.xhr.ontimeout = option.error || null;

        this.xhr.onreadystatechange = event => {
        
            if(event.target.readyState === 4 && event.target.status === 200){

                if(this.success !== null) this.success(event.target.responseText, event);
                
            }

            else if(this.change !== null) this.change(event);

        }

        if(option.data) this.send(option.data);
    }

    send(data = ""){
        if(this.method === "get"){
            this.xhr.open(this.method, this.url+"?"+data, this.asy);
            this.xhr.send();
        }
        
        else if(this.method === "post"){
            this.xhr.open(this.method, this.url, this.asy);
            this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            this.xhr.send(data);
        }
    }
    
}




/* IndexedDB 本地数据库

parameter:
    name: String;                //需要打开的数据库名称(如果不存在则会新建一个) 必须
    done: Function(IndexedDB);    //链接数据库成功时的回调 默认 null
    version: Number;             //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 

attribute:
    database: IndexedDB;            //链接完成的数据库对象
    transaction: IDBTransaction;    //事务管理(读和写)
    objectStore: IDBObjectStore;    //当前的事务

method:
    set(data, key, callback)        //添加或更新
    get(key, callback)                //获取
    delete(key, callback)            //删除

    traverse(callback)                //遍历
    getAll(callback)                //获取全部
    clear(callback)                    //清理所以数据
    close()                         //关闭数据库链接

readOnly:

static:
    indexedDB: Object;

demo:
    
    new IndexedDB('TEST', db => {

        conosle.log(db);

    });

*/
class IndexedDB{

    static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

    get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建
        return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
    }

    constructor(name, done = null, version = 1){

        if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
        
        if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误');

        this.name = name;
        this.database = null;

        const request = IndexedDB.indexedDB.open(name, version);
        
        request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发
            if(!this.database) this.database = e.target.result;
            if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
        }
        
        request.onsuccess = (e)=>{
            this.database = e.target.result;
            if(typeof done === 'function') done(this);
        }
        
        request.onerror = (e)=>{
            console.error(e);
        }
        
    }

    close(){

        return this.database.close();

    }

    clear(callback){
        
        this.objectStore.clear().onsuccess = callback;
        
    }

    traverse(callback){
        
        this.objectStore.openCursor().onsuccess = callback;

    }

    set(data, key = 0, callback){
        
        this.objectStore.put(data, key).onsuccess = callback;

    }
    
    get(key = 0, callback){

        this.objectStore.get(key).onsuccess = callback;
        
    }

    del(key = 0, callback){

        this.objectStore.delete(key).onsuccess = callback;

    }
    
    getAll(callback){

        this.objectStore.getAll().onsuccess = callback;

    }

}




/* TreeStruct 树结构基类

attribute:
    parent: TreeStruct;
    children: Array[TreeStruct];

method:
    add(v: TreeStruct): v;         //v添加到自己的子集
    remove(v: TreeStruct): v;     //删除v, 前提v必须是自己的子集
    export(): Array[Object];    //TreeStruct 转为 可导出的结构, 包括其所有的后代

    getPath(v: TreeStruct): Array[TreeStruct];     //获取自己到v的路径

    traverse(callback: Function): undefined;  //迭代自己的每一个后代, 包括自己
        callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代);

    traverseUp(callback): undefined; //向上遍历每一个父, 包括自己
        callback(value: TreeStruct); //如返回 "break" 立即停止遍历;

static:
    import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct

*/
class TreeStruct{

    static import(arr){

        //json = JSON.parse(json);
        const len = arr.length;

        for(let k = 0, v; k < len; k++){
            v = Object.assign(new TreeStruct(), arr[k]);
            v.parent = arr[arr[k].parent] || null;
            if(v.parent !== null) v.parent.add(v);
            arr[k] = v;
        }

        return arr[0];

    }

    constructor(){
        this.parent = null;
        this.children = [];
    }

    getPath(v){

        var path;

        const pathA = [];
        this.traverseUp(tar => {
            if(v === tar){
                path = pathA;
                return "break";
            }
            pathA.push(tar);
        });

        if(path) return path;

        const pathB = [];
        v.traverseUp(tar => {
            if(this === tar){
                path = pathB.reverse();
                return "break";
            }
            else{
                let i = pathA.indexOf(tar);
                if(i !== -1){
                    pathA.splice(i);
                    pathA.push(tar);
                    path = pathA.concat(pathB.reverse());
                    return "break";
                }
            }
            pathB.push(tar);
        });

        return path;
        
    }

    add(v){
        v.parent = this;
        if(this.children.includes(v) === false) this.children.push(v);
        
        return v;
    }

    remove(v){
        const i = this.children.indexOf(v);
        if(i !== -1) this.children.splice(i, 1);
        v.parent = null;

        return v;
    }

    traverse(callback, key = 0){

        if(callback(this, key) !== "continue"){

            for(let k = 0, len = this.children.length; k < len; k++){

                this.children[k].traverse(callback, k);
    
            }

        }

    }

    traverseUp(callback){

        var par = this.parent;

        while(par !== null){
            if(callback(par) === "break") return;
            par = par.parent;
        }

    }

    export(){

        const result = [], arr = [];
        var obj = null;

        this.traverse(v => {
            obj = Object.assign({}, v);
            obj.parent = arr.indexOf(v.parent);
            delete obj.children;
            result.push(obj);
            arr.push(v);
        });
        
        return result; //JSON.stringify(result);

    }

}

Object.defineProperties(TreeStruct.prototype, {
    
    isTreeStruct: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});



/* Point
parameter: 
    x = 0, y = 0;

attribute
    x, y: Number;

method:
    set(x, y): this;
    angle(): Number;
    copy(point): this;
    clone(): Point;
    distance(point): Number;            //获取欧几里得距离
    distanceMHD(point): Number;            //获取曼哈顿距离
    distanceCompare(point): Number;        //获取用于比较的距离(相对于.distance() 效率更高)
    equals(point): Bool;                //是否恒等
    reverse(): this;                    //取反值
    rotate(origin: Object{x,y}, angle): this;    //旋转点
    normalize(): this;                    //归一
*/
class Point{

    constructor(x = 0, y = 0){
        this.x = x;
        this.y = y;
    }

    set(x = 0, y = 0){
        this.x = x;
        this.y = y;

        return this;
    }

    angle(){

        return Math.atan2(this.y, this.x);

    }

    copy(point){
        
        return Object.assign(this, point);

    }
    
    clone(){

        return Object.assign(new this.constructor(), this);
        
    }

    distance(point){
        
        return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));

    }

    distanceMHD(point){

        return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);

    }

    distanceCompare(point){
    
        return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);

    }

    equals(point){

        return point.x === this.x && point.y === this.y;

    }

    reverse(){
        this.x = -this.x;
        this.y = -this.y;

        return this;
    }

    rotate(origin, angle){
        const c = Math.cos(angle), s = Math.sin(angle), 
        x = this.x - origin.x, y = this.y - origin.y;

        this.x = x * c - y * s + origin.x;
        this.y = x * s + y * c + origin.y;

        return this;
    }

    normalize(){
        const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
        this.x *= len;
        this.y *= len;

        return this;
    }

/*     add(point){
        this.x += point.x;
        this.y += point.y;
        return this;
    }

    sub(point){
        this.x -= point.x;
        this.y -= point.y;
        return this;
    }

    multiply(point){
        this.x *= point.x;
        this.y *= point.y;
        return this;
    }

    divide(point){
        this.x /= point.x;
        this.y /= point.y;
        return this;
    } */

}

Object.defineProperties(Point.prototype, {

    isPoint: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Line
parameter: x, y, x1, y1: Number;
attribute: x, y, x1, y1: Number;
method:
    set(x, y, x1, y1): this;                    
    containsPoint(x, y): Bool;                             //点是否在线上
    intersectPoint(line: Line, point: Point): Point;    //如果不相交则返回null, 否则返回交点Point
    isIntersect(line): Bool;                             //this与line是否相交

*/
class Line{

    constructor(x = 0, y = 0, x1 = 0, y1 = 0){
        this.x = x;
        this.y = y;
        this.x1 = x1;
        this.y1 = y1;

    }

    set(x = 0, y = 0, x1 = 0, y1 = 0){
        this.x = x;
        this.y = y;
        this.x1 = x1;
        this.y1 = y1;
        return this;
    }

    containsPoint(x, y){

        return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;

    }

    intersectPoint(line, point){
        //解线性方程组, 求线段交点
        //如果分母为0则平行或共线, 不相交
        var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
        if(denominator === 0) return null;

        //线段所在直线的交点坐标 (x , y)
        const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
        + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
        - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;

        const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
        + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
        - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;

        //判断交点是否在两条线段上
        if(this.containsPoint(x, y) && line.containsPoint(x, y)){
            point.x = x;
            point.y = y;
            return point;
        }

        return null;
    }

    isIntersect(line){
        //快速排斥:
        //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的

        //这里的确如此,这一步是判定两矩形是否相交
        //1.线段ab的低点低于cd的最高点(可能重合)
        //2.cd的最左端小于ab的最右端(可能重合)
        //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
        //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
        //综上4个条件,两条线段组成的矩形是重合的
        //特别要注意一个矩形含于另一个矩形之内的情况
        if(!(Math.min(this.x,this.x1)<=Math.max(line.x,line.x1) && Math.min(line.y,line.y1)<=Math.max(this.y,this.y1) && Math.min(line.x,line.x1)<=Math.max(this.x,this.x1) && Math.min(this.y,this.y1)<=Math.max(line.y,line.y1))) return false;

        //跨立实验:
        //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
        //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
        var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
        v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
        w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
        z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
        
        return u*v <= 0.00000001 && w*z <= 0.00000001;
    }

}




/* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间)
parameter: 
    width = 0, height = 0

attribute: 
    width, height: Number;     //矩形的宽高
    position: Point;        //位置(是旋转和缩放的中心点)
    rotation: Number;        //旋转(绕 Z 轴旋转的弧度)
    scale: Point;            //缩放

method:
    setFromBox(box): this;                //Box 转为 ShapeRect
    compute(): undefined;                //计算出矩形
    applyCanvas(context): undefined;    //矩形应用到画布的上下文中

demo:
    const canvasRect = new ShapeRect(100, 150); console.log(canvasRect);

    const canvas = document.createElement("canvas");
    canvas.width = WORLD.width;
    canvas.height = WORLD.height;
    canvas.style = `
        position: absolute;
        z-index: 9999;
        background: rgb(127,127,127);
    `;
    document.body.appendChild(canvas);

    canvasRect.position.set(300, 300);
    canvasRect.rotation = UTILS.toAngle(45);
    canvasRect.scale.set(1.5, 1.5);
    canvasRect.compute();

    const context = canvas.getContext("2d");
    canvasRect.applyCanvas(context);

    context.strokeStyle = "red";
    context.stroke();
*/
class ShapeRect{

    #dots = [];

    constructor(width = 0, height = 0){
        this.width = width;
        this.height = height;
        this.position = new Point();
        this.rotation = 0;
        this.scale = new Point(1, 1);
    }

    setFromBox(box){
        this.width = box.w;
        this.height = box.h;
        this.position.set(box.cx, box.cy);
        return this;
    }

    compute(){
        //scale
        const width = this.width * this.scale.x, 
        height = this.height * this.scale.y,

        //position
        minX = this.position.x - width / 2, 
        minY = this.position.y - height / 2,
        maxX = minX + width, 
        maxY = minY + height,
        
        //rotation
        point = UTILS.emptyPoint;
        this.#dots.length = 0;

        point.set(minX, minY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(maxX, minY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(maxX, maxY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(minX, maxY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);
    }

    applyCanvas(context){
        context.beginPath();
        context.moveTo(this.#dots[0], this.#dots[1]);

        for(let k = 2, len = this.#dots.length; k < len; k += 2){
            context.lineTo(this.#dots[k], this.#dots[k + 1]);
        }

        context.closePath();
    }

}




/* Box 矩形

parameter: 
    x = 0, y = 0, w = 0, h = 0;

attribute:
    x,y: Number; 位置
    w,h: Number; 大小

    只读
    mx, my: Number; //

method:
    set(x, y, w, h): this;
    pos(x, y): this; //设置位置
    size(w, h): this; //设置大小
    setFromShapeRect(shapeRect): this;            //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放)
    setFromCircle(circle, inner: Bool): this;    //
    toArray(array: Array, index: Integer): this;
    copy(box): this;                             //复制
    clone(): Box;                                  //克隆
    center(box): this;                            //设置位置在box居中
    distance(x, y): Number;                     //左上角原点 与 x,y 的直线距离
    isEmpty(): Boolean;                         //.w.h是否小于等于零
    maxX(): Number;                             //返回 max x(this.x+this.w);
    maxY(): Number;                             //返回 max y(this.y+this.h);
    expand(box): undefined;                     //扩容; 把box合并到this
    equals(box): Boolean;                         //this与box是否恒等
    intersectsBox(box): Boolean;                 //box与this是否相交(box在this内部也会返回true)
    containsPoint(x, y): Boolean;                 //x,y点是否在this内
    containsBox(box): Boolean;                    //box是否在this内(只是相交返回fasle)
    computeOverflow(b: Box, r: Box): undefined;    //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出
    
*/
class Box{

    get mx(){
        return this.x + this.w;
    }

    get my(){
        return this.y + this.h;
    }

    get cx(){
        return this.w / 2 + this.x;
    }

    get cy(){
        return this.h / 2 + this.y;
    }

    constructor(x = 0, y = 0, w = 0, h = 0){
        //this.set(x, y, w, h);
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }
    
    set(x, y, w, h){
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        return this;
    }

    pos(x, y){
        this.x = x;
        this.y = y;
        return this;
    }
    
    size(w, h){
        this.w = w;
        this.h = h;
        return this;
    }

    setFromShapeRect(shapeRect){
        this.width = shapeRect.width;
        this.height = shapeRect.height;
        this.x = shapeRect.position.x - this.width / 2;
        this.y = shapeRect.position.y - this.height / 2;
        return this;
    }

    setFromCircle(circle, inner = true){
        if(inner === true){
            this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
            this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
            this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
        }

        else{
            this.x = circle.x - circle.r;
            this.y = circle.y - circle.r;
            this.w = this.h = circle.r * 2;
        }
        return this;
    }

    setFromPolygon(polygon, inner = true){
        if(inner === true){
            console.warn('Box: 暂不支持第二个参数为true');
        }

        else{
            const len = polygon.path.length;
            let x = Infinity, y = Infinity, mx = 0, my = 0;
            for(let k = 0, v; k < len; k+=2){
                v = polygon.path[k];
                if(v < x) x = v;
                else if(v > mx) mx = v;

                v = polygon.path[k+1];
                if(v < y) y = v;
                else if(v > my) my = v;

            }

            this.set(x, y, mx - x, my - y);

        }
        return this;
    }

    toArray(array, index){
        array[index] = this.x;
        array[index+1] = this.y;
        array[index+2] = this.w;
        array[index+3] = this.h;

        return this;
    }

    copy(box){
        /* this.x = box.x;
        this.y = box.y;
        this.w = box.w;
        this.h = box.h; */
        return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
    }
    
    clone(){
        //return new this.constructor().copy(this);
        return Object.assign(new this.constructor(), this);
    }

    center(box){
        this.x = (box.w - this.w) / 2 + box.x;
        this.y = (box.h - this.h) / 2 + box.y;
        return this;
    }

    distance(x, y){
        return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
    }

    isEmpty(){
        return this.w <= 0 || this.h <= 0;
    }

    maxX(){
        return this.x + this.w;
    }

    maxY(){
        return this.y + this.h;
    }

    equals(box){
        return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
    }

    expand(box){
        var v = Math.min(this.x, box.x);
        this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
        this.x = v;

        v = Math.min(this.y, box.y);
        this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
        this.y = v;
    }

    intersectsBox(box){
        return box.x + box.w < this.x || box.x > this.x + this.w || box.y + box.h < this.y || box.y > this.y + this.h ? false : true;
    }

    containsPoint(x, y){
        return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
    }

    containsBox(box){
        return this.x <= box.x && box.x + box.w <= this.x + this.w && this.y <= box.y && box.y + box.h <= this.y + this.h;
    }

    computeOverflow(p, r){
        r["copy"](this);
        
        if(this["x"] < p["x"]){
            r["x"] = p["x"];
            r["w"] -= p["x"] - this["x"];
        }

        if(this["y"] < p["y"]){
            r["y"] = p["y"];
            r["h"] -= p["y"] - this["y"];
        }

        var m = p["x"] + p["w"];
        if(r["x"] + r["w"] > m) r["w"] = m - r["x"];

        m = p["y"] + p["h"];
        if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
    }
    
}

Object.defineProperties(Box.prototype, {

    isBox: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    },

});




/* Circle 圆形
parameter:
attribute:
    x,y: Number; 中心点
    r: Number; 半径

    //只读
    r2: Number; //返回直径 r*2

method:
    set(x, y, r): this;
    pos(x, y): this;
    copy(circle: Circle): this;
    clone(): Circle;
    distance(x, y): Number;
    equals(circle: Circle): Bool;
    containsPoint(x, y): Bool; 
    intersectsCircle(circle: Circle): Bool;
    intersectsBox(box: Box): Bool;
    setFromBox(box, inner = true): this;

*/
class Circle{

    get r2(){
        return this.r * 2;
    }

    constructor(x = 0, y = 0, r = -1){
        //this.set(0, 0, -1);
        this.x = x;
        this.y = y;
        this.r = r;
    }

    set(x, y, r){
        this.x = x;
        this.y = y;
        this.r = r;

        return this;
    }

    setFromBox(box, world = true, inner = true){
        this.x = box.w / 2 + (world === true ? box.x : 0);
        this.y = box.h / 2 + (world === true ? box.y : 0);
        this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2;
    
        return this;
    }

    toArray(array, index){
        array[index] = this.x;
        array[index+1] = this.y;
        array[index+2] = this.r;
        
        return this;
    }

    pos(x, y){
        this.x = x;
        this.y = y;

        return this;
    }

    copy(circle){
        this.r = circle.r;
        this.x = circle.x;
        this.y = circle.y;

        return this;
    }

    clone(){

        return new this.constructor().copy(this);

    }

    distance(x, y){
        
        return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));

    }

    equals(circle){

        return circle.x === this.x && circle.y === this.y && circle.r === this.r;

    }

    containsPoint(x, y){

        return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2));

    }

    intersectsCircle(circle){

        return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));

    }

    intersectsBox(box){
        
        return (Math.pow(Math.max(box.x, Math.min(box.x + box.w, this.x)) - this.x, 2) + Math.pow(Math.max(box.y, Math.min(box.y + box.h, this.y)) - this.y, 2) <= Math.pow(this.r, 2));
    
    }

}

Object.defineProperties(Circle.prototype, {

    isCircle: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Polygon 多边形

parameter: 
    path: Array[x, y];

attribute:

    //只读属性
    path: Array[x, y]; 

method:
    add(x, y): this;             //x,y添加至path;
    containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)
    
*/
class Polygon{

    #position = null;
    #path2D = null;

    get path(){
        
        return this.#position;

    }

    constructor(path = []){
        this.#position = path;

        this.#path2D = new Path2D();
        
        var len = path.length;
        if(len >= 2){
            if(len % 2 !== 0){
                len -= 1;
                path.splice(len, 1);
            }

            const con = this.#path2D;
            con.moveTo(path[0], path[1]);
            for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);
            
        }

    }

    add(x, y){
        this.#position.push(x, y);
        this.#path2D.lineTo(x, y);
        return this;
    }

    containsPoint(x, y){
        
        return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);

    }

    isInPolygon(checkPoint, polygonPoints) {
        var counter = 0;
        var i;
        var xinters;
        var p1, p2;
        var pointCount = polygonPoints.length;
        p1 = polygonPoints[0];
        for (i = 1; i <= pointCount; i++) {
            p2 = polygonPoints[i % pointCount];
            if (
                checkPoint[0] > Math.min(p1[0], p2[0]) &&
                checkPoint[0] <= Math.max(p1[0], p2[0])
            ) {
                if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                    if (p1[0] != p2[0]) {
                        xinters =
                            (checkPoint[0] - p1[0]) *
                                (p2[1] - p1[1]) /
                                (p2[0] - p1[0]) +
                            p1[1];
                        if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
                            counter++;
                        }
                    }
                }
            }
            p1 = p2;
        }
        if (counter % 2 == 0) {
            return false;
        } else {
            return true;
        }
    }

    containsPolygon(polygon){
        const path = polygon.path, len = path.length;
        for(let k = 0; k < len; k += 2){
            if(this.containsPoint(path[k], path[k+1]) === false) return false;
        }

        return true;
    }

    toPoints(){
        const path = this.path, len = path.length, result = [];
        
        for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));

        return result;
    }

    toLines(){
        const path = this.path, len = path.length, result = [];
        
        for(let k = 0, x = NaN, y; k < len; k += 2){

            if(isNaN(x)){
                x = path[k];
                y = path[k+1];
                continue;
            }

            const line = new Line(x, y, path[k], path[k+1]);
            
            x = line.x1;
            y = line.y1;

            result.push(line);

        }

        return result;
    }

    merge(polygon){

        const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
        
        pointA = new Point(), pointB = new Point(),

        forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
            for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
                if(funcA !== null) funcA(pathA[k]);

                for(let i = 0; i < lenB; i++){
                    if(funcB !== null) funcB(pathB[i], pathA[k]);
                }
    
            }
        }

        if(this.containsPolygon(polygon)){console.log('this -> polygon');
            forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
                if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
            });

            return newLines;
        }

        //收集所有的交点 (保存至 line)
        forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
            if(lineB.nodes === undefined) lineB.nodes = [];
            if(lineA.intersectPoint(lineB, pointA) === pointA){
                const node = {
                    lineA: lineA, 
                    lineB: lineB, 
                    point: pointA.clone(),
                    disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
                    disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
                }
                lineA.nodes.push(node);
                lineB.nodes.push(node);
                nodes.push(node);
            }
        });

        //交点以原点为目标排序
        for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
        for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);

        var _loopTypeA, _loopTypeB;
        const result_loop = {
            lines: null,
            loopType: '',
            line: null,
            count: 0,
            indexed: 0,
        },
        
        //遍历某条线
        loop = (lines, index, loopType) => {
            const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
        
            var line, i = 1;
            while(true){
                if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
                else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
                line = lines[index];

                result_loop.count = line.nodes.length;
                if(result_loop.count !== 0){
                    result_loop.lines = lines;
                    result_loop.loopType = loopType;
                    result_loop.line = line;
                    result_loop.indexed = index;
                    if(loopType === 'next') addLine(line, model);

                    return result_loop;
                }
                
                addLine(line, model);
                if(indexed === i++) break;

            }
            
        },

        //更新或创建交点的索引
        setNodeIndex = (lines, index, loopType) => {
            const line = lines[index], count = line.nodes.length;
            if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;

            if(loopType === undefined) return;
            
            if(line.nodeIndex === undefined){
                line.nodeIndex = loopType === 'next' ? 0 : count - 1;
                line.nodeState = count === 1 ? 'end' : 'start';
            
            }

            else{
                if(line.nodeState === 'end' || line.nodeState === ''){
                    line.nodeState = '';
                    return;
                }

                if(loopType === 'next'){
                    line.nodeIndex += 1;

                    if(line.nodeIndex === count - 1) line.nodeState = 'end';
                    else line.nodeState = 'run';
                }

                else if(loopType === 'back'){
                    line.nodeIndex -= 1;

                    if(line.nodeIndex === 0) line.nodeState = 'end';
                    else line.nodeState = 'run';
                }

            }

        },

        //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端;
        getLoopType = (lines, index, nodePoint) => {
            const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],

            model = lines === linesA ? polygon : this,
            isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
            isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
            
            if(isLineBack && isLineNext){
                const len = line.nodes.length;
                if(len >= 2){
                    if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
                    else if(line.nodes[0].point.equals(nodePoint)) return 'back';
                }
                
                else console.warn('路径复杂', line);
                
            }

            else if(isLineNext){
                return 'next';
            }

            else if(isLineBack){
                return 'back';
            }

            return '';
        },

        //添加线至新的形状数组
        addLine = (line, model) => {
            //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
            if(line.nodes.length === 0) newLines.push(line);
            else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
            
        },

        //处理拥有交点的线
        computeNodes = v => {
            if(v === undefined || v.count === 0) return;
            
            setNodeIndex(v.lines, v.indexed, v.loopType);
        
            //添加交点
            const node = v.line.nodes[v.line.nodeIndex];
            if(newLines.includes(node.point) === false) newLines.push(node.point);
            else return;

            var lines = v.lines === linesA ? linesB : linesA, 
            line = lines === linesA ? node.lineA : node.lineB, 
            index = lines.indexOf(line);

            setNodeIndex(lines, index);
        
            //选择交点状态
            var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
            if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
                if(line.nodeState === 'start'){
                    const backLine = v.loopType === 'next' ? v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1] : v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1];
                    
                    if(newLines.includes(backLine) && backLine.nodes.length === 0){
                        nodeState = 'run';
                    }

                }
                else if(line.nodeState === 'end'){
                    const nextLine = v.loopType === 'next' ? v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1] : v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1];
                    const model = v.lines === linesA ? polygon : this;
                    if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
                        nodeState = 'run';
                    }
                    
                }
            }

            switch(nodeState){

                //不跳线
                case 'run': 
                    if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
                    return computeNodes(loop(v.lines, v.indexed, v.loopType));

                //跳线
                case 'start': 
                case 'end': 
                    const loopType = getLoopType(lines, index, node.point);
                    if(loopType !== ''){
                        if(lines === linesA) _loopTypeA = loopType;
                        else _loopTypeB = loopType;
                        if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
                        return computeNodes(loop(lines, index, loopType));
                    }
                    break;

            }

        }
        
        //获取介入点
        var startLine = null;
        for(let k = 0, len = nodes.length, node; k < len; k++){
            node = nodes[k];
            if(node.lineA.nodes.length !== 0){
                startLine = node.lineA;
                if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
                    startLine = node.lineB.nodes[0].lineB;
                    result_loop.lines = linesB;
                    result_loop.loopType = _loopTypeB = 'next';
                }
                else{
                    result_loop.lines = linesA;
                    result_loop.loopType = _loopTypeA = 'next';
                }
                result_loop.line = startLine;
                result_loop.count = startLine.nodes.length;
                result_loop.indexed = result_loop.lines.indexOf(startLine);
                break;
            }
        }

        if(startLine === null){
            console.warn('Polygon: 找不到介入点, 终止了合并');
            return newLines;
        }

        computeNodes(result_loop);
    
        return newLines;
    }

}




/* RGBColor
parameter: 
    r, g, b

method:
    set(r, g, b: Number): this;            //rgb: 0 - 255; 第一个参数可以为 css color
    setFormHex(hex: Number): this;         //
    setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 颜色, 明度, 暗度
    setFormString(str: String): Number;    //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1)

    copy(v: RGBColor): this;
    clone(): RGBColor;

    getHex(): Number;
    getHexString(): String;
    getHSV(result: Object{h, s, v}): result;    //result: 默认是一个新的Object
    getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默认 1
    getStyle()                                     //.getRGBA()别名

    stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 ""

*/
class RGBColor{

    constructor(r = 255, g = 255, b = 255){
        this.r = r;
        this.g = g;
        this.b = b;

    }

    copy(v){
        this.r = v.r;
        this.g = v.g;
        this.b = v.b;
        return this;
    }

    clone(){
        return new this.constructor().copy(this);
    }

    set(r, g, b){
        if(typeof r !== "string"){
            this.r = r || 255;
            this.g = g || 255;
            this.b = b || 255;
        }

        else this.setFormString(r);
        
        return this;
    }

    setFormHex(hex){
        hex = Math.floor( hex );

        this.r = hex >> 16 & 255;
        this.g = hex >> 8 & 255;
        this.b = hex & 255;
        return this;
    }

    setFormHSV(h, s, v){
        h = h >= 360 ? 0 : h;
        var s=s/100;
        var v=v/100;
        var h1=Math.floor(h/60) % 6;
        var f=h/60-h1;
        var p=v*(1-s);
        var q=v*(1-f*s);
        var t=v*(1-(1-f)*s);
        var r,g,b;
        switch(h1){
            case 0:
                r=v;
                g=t;
                b=p;
                break;
            case 1:
                r=q;
                g=v;
                b=p;
                break;
            case 2:
                r=p;
                g=v;
                b=t;
                break;
            case 3:
                r=p;
                g=q;
                b=v;
                break;
            case 4:
                r=t;
                g=p;
                b=v;
                break;
            case 5:
                r=v;
                g=p;
                b=q;
                break;
        }

        this.r = Math.round(r*255);
        this.g = Math.round(g*255);
        this.b = Math.round(b*255);
        return this;
    }

    setFormString(color){
        if(typeof color !== "string") return 1;
        var _color = this.stringToColor(color);
        
        if(_color[0] === "#"){
            const len = _color.length;
            if(len === 4){
                _color = _color.slice(1);
                this.setFormHex(parseInt("0x"+_color + "" + _color));
            }
            else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
            
        }

        else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
            const arr = [];
            for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
                
                if(is === true){
                    if(_color[k] === "," || _color[k] === ")"){
                        arr.push(parseFloat(v));
                        v = "";
                    }
                    else v += _color[k];
                    
                }

                else if(_color[k] === "(") is = true;
                
            }

            this.set(arr[0], arr[1], arr[2]);
            return arr[3] === undefined ? 1 : arr[3];
        }
        
        return 1;
    }

    getHex(){

        return Math.max( 0, Math.min( 255, this.r ) ) << 16 ^ Math.max( 0, Math.min( 255, this.g ) ) << 8 ^ Math.max( 0, Math.min( 255, this.b ) ) << 0;

    }

    getHexString(){

        return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );

    }

    getHSV(result){
        result = result || {}
        var r=this.r/255;
        var g=this.g/255;
        var b=this.b/255;
        var h,s,v;
        var min=Math.min(r,g,b);
        var max=v=Math.max(r,g,b);
        var l=(min+max)/2;
        var difference = max-min;
        
        if(max==min){
            h=0;
        }else{
            switch(max){
                case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
                case g: h=2.0+(b-r)/difference;break;
                case b: h=4.0+(r-g)/difference;break;
            }
            h=Math.round(h*60);
        }
        if(max==0){
            s=0;
        }else{
            s=1-min/max;
        }
        s=Math.round(s*100);
        v=Math.round(v*100);
        result.h = h;
        result.s = s;
        result.v = v;
        return result;
    }

    getStyle(){
        return this.getRGBA(1);
    }

    getRGBA(alpha){
        alpha = typeof alpha === 'number' ? alpha : 1;
        return 'rgba('+this.r+','+this.g+','+this.b+','+alpha+')';
    }

    stringToColor(str){
        var _color = "";
        for(let k = 0, len = str.length; k < len; k++){
            if(str[k] === " ") continue;
            _color += str[k];
        }
        
        if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
        else{
            for(let k = 0, len = ColorRefTable.length; k < len; k++){
                str = ColorRefTable[k];
                if(str[0] === _color) return str[1];
            }
        }

        return "";
    }

}

Object.defineProperties(RGBColor.prototype, {

    isRGBColor: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Timer 定时器 

parameter:
    func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法
    speed: Number; //延迟多少毫秒执行一次 func; 默认 3000;
    step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity;
    
attribute:
    func, speed, step;    //这些属性可以随时更改;

    //只读属性
    readyState: String;    //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动
    number: Number;        //运行的次数

method:
    start(func, speed): this;    //启动定时器 (如果定时器正在运行则什么都不会做)
    restart(): undefined;        //重启定时器
    stop(): undefined;            //停止定时器

demo:
    //每 3000 毫秒 打印一次 timer.number, 10次后停止
    new Timer(timer => {
        console.log(timer.number);
        if(timer.number === 10) timer.stop();
    }, 3000);

*/
class Timer{

    #restart = -1;
    #speed = 0;
    #isRun = false;
    #i = 0;
    #readyState = ''; //start|running

    get number(){
        return this.#i;
    }
    
    get readyState(){
        return this.#i >= this.step ? 'done' : this.#readyState;
    }

    get running(){
        return this.#isRun;
    }

    constructor(func = null, speed = 3000, step = Infinity){
        this.func = func;
        this.speed = speed;
        this.step = step;
        //this.onDone = null;
    
        if(typeof this.func === "function") this.restart();

    }

    start(func, time){
        if(typeof func === 'function') this.func = func;
        if(UTILS.isNumber(time) === true) this.speed = time;
        this.restart();

        return this;
    }

    restart(){
        if(this.#isRun === false){
            setTimeout(this._loop, this.speed);
            this.#isRun = true;
            this.#restart = -1;
            this.#i = 0;
            this.#readyState = 'start';
            
        }

        else{
            this.#restart = Date.now();
            this.#speed = this.speed;

        }

    }

    stop(){
        if(this.#isRun === true){
            this.#restart = -1;
            this.#i = this.step;
        }

    }

    _loop = () => {

        //重启计时器
        if(this.#restart !== -1){
            
            let gone = Date.now() - this.#restart;
            this.#restart = -1;
            
            if(gone >= this.#speed) gone = this.speed;
            else{
                if(this.#speed === this.speed) gone = this.#speed - gone;
                else gone = (this.#speed - gone) / this.#speed * this.speed;
            }
            
            setTimeout(this._loop, gone);
            
            this.#i = 1;
            if(this.func !== null) this.func(this);

        }

        //正在运行
        else if(this.#i < this.step){

            setTimeout(this._loop, this.speed);

            this.#i++;
            if(this.#readyState !== 'running') this.#readyState = 'running';
            if(this.func !== null) this.func(this);

        }

        //完成
        else this.#isRun = false;

    }

}




/* SeekPath A*寻路

parameter: 
    option: Object{
        angle: Number,         //8 || 16
        timeout: Number,     //单位为毫秒
        size: Number,         //每格的宽高
        lenX, lenY: Number,    //长度
        disables: Array[0||1],
        heights: Array[Number],
        path: Array[], //存放寻路结果 默认创建一个空数组
    }

attribute:
    size: Number;     //每个索引的大小
    lenX: Number;     //最大长度x (设置此属性时, 你需要重新.initMap(heights);)
    lenY: Number;     //最大长度y (设置此属性时, 你需要重新.initMap(heights);)

    //此属性已废弃 range: Box;            //本次的搜索范围, 默认: 0,0,lenX,lenY
    angle: Number;         //8四方向 或 16八方向 默认 16
    timeout: Number;     //超时毫秒 默认 500
    mapRange: Box;        //地图box
    //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number;     //相邻可走的最大高 默认 6

    //只读属性
    success: Bool;            //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
    path: Array[x, y, z];    //存放.run()返回的路径
    map: Map;                 //地图的缓存数据

method:
    initMap(heights: Array[Number]): undefiend;     //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
    run(x, y, x1, y1: Number): Array[x, y, z];         //参数索引坐标
    getDots(x, y, a, r): Array[ix, iy];             //获取周围的点 x,y, a:8|16, r:存放结果数组
    getLegalPoints(ix, iy, count): Array[x, y, z];    //获取 ix, iy 周围 合法的, 相邻的 count 个点

demo:
    const sp = new SeekPath({
        angle: 16,
        timeout: 500,
        //maxHeight: 6,
        size: 10,
        lenX: 1000,
        lenY: 1000,
    }),

    path = sp.run(0, 0, 1000, 1000);

    console.log(sp);

*/
class SeekPath{

    static _open = []
    static _dots = [] //.run() .getLegalPoints()
    static dots4 = []; //._check()
    static _sort = function (a, b){return a["f"] - b["f"];}

    #map = null;
    #path = null;
    #success = true;
    #halfX = 50;
    #halfY = 50;

    #size = 10;
    #lenX = 10;
    #lenY = 10;

    constructor(option = {}){
        this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
        this.timeout = option.timeout || 500; //超时毫秒
        //this.maxHeight = option.maxHeight || 6;
        this.mapRange = new Box();
        this.size = option.size || 10;
        this.lenX = option.lenX || 10;
        this.lenY = option.lenY || 10;
        this.#path = Array.isArray(option.path) ? option.path : [];
        this.initMap(option.disable, option.height);
        option = undefined
    }

    //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)};
    get map(){
        return this.#map;
    }

    //this.#path = Array[x,y,z]
    get path(){
        return this.#path;
    }

    get success(){
        return this.#success;
    }

    get size(){
        return this.#size;
    }

    set size(v){
        this.#size = v;
        v = v / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
    }

    get lenX(){
        return this.#lenX;
    }

    set lenX(v){
        this.#lenX = v;
        v = this.#size / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
        
    }

    get lenY(){
        return this.#lenY;
    }

    set lenY(v){
        this.#lenY = v;
        v = this.#size / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
        
    }

    toScene(n, v){ //n = "x|y"
        //n = n === "y" ? "lenY" : "lenX";
        if(n === "y" || n === "z") return v * this.#size - this.#halfY;
        return v * this.#size - this.#halfX;
    
    }
    
    toIndex(n, v){
        //n = n === "y" ? "lenY" : "lenX";
        if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size);
        return Math.round((this.#halfX + v) / this.#size);

    }

    initMap(disable, height){
        
        disable = Array.isArray(disable) === true ? disable : null;
        height = Array.isArray(height) === true ? height : null;
        
        const lenX = this.lenX, lenY = this.lenY;
        var getHeight = (ix, iy) => {
            if(height === null) return 0;
            ix = height[ix * lenY + iy];
            if(ix === undefined) return 0;
            return ix;
        },
        getDisable = (ix, iy) => {
            if(disable === null) return 1;
            ix = disable[ix * lenY + iy];
            if(ix === undefined) return 0;
            return ix;
        },

        map = []//new Map();

        for(let x = 0, y, m; x < lenX; x++){
            m = []//new Map();
            for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), is:getDisable(x, y),  g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y),   g:0, h:0, f:0, p:null, id:""});
            map[x] = m;//map.set(x, m);
        }
        
        this.#map = map;
        this._id = -1;
        this._updateID();
        this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);

        map = disable = height = getHeight = undefined;

    }

    getLegalPoints(ix, iy, count, result = []){
        const _dots = SeekPath._dots;
        result.length = 0;
        result[0] = this.#map[ix][iy];
        count += 1;
        
        while(result.length < count){
            for(let k = 0, i, n, d, len = result.length; k < len; k++){
                n = result[k];
                this.getDots(n.x, n.y, this.angle, _dots);
                for(i = 0; i < this.angle; i += 2){
                    d = this.#map[_dots[i]][_dots[i+1]];
                    if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
                        if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
                            result.push(d);
                        }
                    }
                }
            }
        }
    
        result.splice(0, 1);
        return result;
    }

    getLinePoints(now, next, count, result = []){
        if(count === 1) return result[0] = next;
        if(count % 2 === 0) count += 1;

        const len = Math.floor(count / 2), 
        nowPoint = UTILS.emptyPoint, 
        angle90 = UTILS.toAngle(90);

        var i, ix, iy, n, nn = next;

        nowPoint.set(now.x, now.y).rotate(next, angle90); //now 以 next 为原点顺时针旋转 90 度
        var disX = nowPoint.x - next.x, 
        disY = nowPoint.y - next.y;
        
        for(i = 0; i < len; i++){
            ix = disX + disX * i + next.x;
            iy = disY + disY * i + next.y;

            n = this.#map[ix][iy];
            if(n.is === 1) nn = n;
            result[len-1-i] = nn;
        }

        result[len] = next;
        nn = next

        nowPoint.set(now.x, now.y).rotate(next, -angle90);
        disX = nowPoint.x - next.x; 
        disY = nowPoint.y - next.y;

        for(i = 0; i < len; i++){
            ix = disX + disX * i + next.x;
            iy = disY + disY * i + next.y;

            n = this.#map[ix][iy];
            if(n.is === 1) nn = n;
            result[len+1+i] = nn;
        }

        return result;
    }

    getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组
        r.length = 0;
        const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
        if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1);
        else r.push(x, y_1, x, y1, x_1, y, x1, y);
    }

    _updateID(){ //更新标记
        this._id++;
        this._openID = "o_"+this._id;
        this._closeID = "c_"+this._id;
    }

    _check(dotA, dotB){ //检测 a 是否能到 b
        //获取 dotB 周围的4个点 并 遍历这4个点
        this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
        for(let k = 0, x, y; k < 8; k += 2){
            x = SeekPath.dots4[k]; 
            y = SeekPath.dots4[k+1];
            if(this.mapRange.containsPoint(x, y) === false) continue;

            //找出 dotA 与 dotB 相交的两个点:
            if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
                //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false
                if(this.#map[x][y].is === 0) return false;
            }

        }

        return true;
    }

    run(x, y, x1, y1, path = this.#path){
        path.length = 0;
        if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
        
        var _n = this.#map[x][y];
        if(_n.is === 0) return path;

        const _sort = SeekPath._sort,
        _open = SeekPath._open,
        _dots = SeekPath._dots, 
        time = Date.now();

        //var isDot = true, 
        var suc = _n, k, mhd, g, h, f, _d;

        _n.g = 0;
        _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
        _n.f = _n.h;
        _n.p = null;
        this._updateID();
        _n.id = this._openID;
        _open.push(_n);
        
        while(_open.length !== 0){
            if(Date.now() - time > this.timeout) break;

            _open.sort(_sort);
            _n = _open.shift();
            if(_n.x === x1 && _n.y === y1){
                suc = _n;
                break;
            }
            
            if(suc.h > _n.h) suc = _n;
            _n.id = this._closeID;
            this.getDots(_n.x, _n.y, this.angle, _dots);
            
            for(k = 0; k < this.angle; k += 2){
                
                _d = this.#map[_dots[k]][_dots[k+1]];
                if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;

                mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
                g = _n["g"] + (mhd === 1 ? 10 : 14);
                h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
                f = g + h;
            
                if(_d.id !== this._openID){
                    //如果是斜角和8方向:
                    if(mhd !== 1 && this.angle === 16){
                        if(this._check(_n, _d)){
                            _d.g = g;
                            _d.h = h;
                            _d.f = f;
                            _d.p = _n;
                            _d.id = this._openID;
                            _open.push(_d);
                        }
                    }else{
                        _d.g = g;
                        _d.h = h;
                        _d.f = f;
                        _d.p = _n;
                        _d.id = this._openID;
                        _open.push(_d);
                    }
                }

                else if(g < _d.g){
                    _d.g = g;
                    _d.f = g + _d.h;
                    _d.p = _n;
                }
    
            }
        }

        this.#success = suc === _n;

        while(suc !== null){
            path.unshift(suc);
            //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
            suc = suc["p"];
        }

        _open.length = _dots.length = 0;
        
        return path;
    }

}




/* RunningList
    如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key;

*/
class RunningList{

    /* static getProxy(runName){

        return new Proxy(new RunningList(runName), {

            get(tar, key){
                
            },

            set(tar, key, val){
                
            }
            
        });

    } */

    constructor(runName = 'update'){
        this._running = false;
        this._list = [];
        this._delList = [];
        this._runName = runName;
    }

    get length(){

        return this._list.length;

    }

    add(v){

        if(!this._list.includes(v)) this._list.push(v);

    }

    clear(){
        this._list.length = 0;
        this._delList.length = 0;
    }

    push(...v){

        v.forEach(_v => this._list.push(_v));

    }

    splice(v){
        if(this._running === true){
            if(!this._delList.includes(v)) this._delList.push(v);
        }

        else{
            const i = this._list.indexOf(v);
            if(i !== -1) this._list.splice(i, 1);
        }

    }

    update(){

        var k, len = this._list.length;

        this._running = true;
        if(this._runName !== ''){
            for(k = 0; k < len; k++) this._list[k][this._runName]();
        }else{
            for(k = 0; k < len; k++) this._list[k]();
        }
        this._running = false;

        var i;
        len = this._delList.length;
        for(k = 0; k < len; k++){
            //this.splice(this._delList[k]);
            i = this._list.indexOf(this._delList[k]);
            if(i !== -1) this._list.splice(i, 1);
        }
        this._delList.length = 0;
        
    }

}




/* TweenValue (从 原点 以规定的时间到达  终点)

parameter: origin, end, time, onUpdate, onEnd;

attribute:
    origin: Object; //原点(起点)
    end: Object; //终点
    time: Number; //origin 到 end 花费的时间
    onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
    onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)

method:
    reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
    reverse(): undefined; //this.end 复制 this.origin 的原始值
    update(): undefined; //Tween 通过此方法统一更新 TweenValue

demo: 
    //init Tween:
    const tween = new Tween(),
    animate = function (){
        requestAnimationFrame(animate);
        tween.update();
    }

    //init TweenValue:
    const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
    
    animate();
    tween.start(v1);

*/
class TweenValue{

    constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){
        this.origin = origin;
        this.end = end;
        this.time = time;

        this.onUpdate = onUpdate;
        this.onEnd = onEnd;
        this.onStart = onStart;
        
        //以下属性不能直接设置
        this._r = null;
        this._t = 0;
        this._v = Object.create(null);

    }

    _start(){
        var v = "";
        for(v in this.origin) this._v[v] = this.origin[v];

        this._t = Date.now();
        //this.update();

    }

    reset(origin, end){
        this.origin = origin;
        this.end = end;
        this._v = Object.create(null);

    }

    reverse(){
        var n = "";
        for(n in this.origin) this.end[n] = this._v[n];

    }

    update(){

        if(this["_r"] !== null){

            var ted = Date["now"]() - this["_t"];

            if(ted >= this["time"]){

                for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];

                if(this["onEnd"] !== null){

                    if(this["onEnd"](this) === "restart"){
                        if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
                        this["_start"]();
                    }

                    else this["_r"]["stop"](this);
                    
                }

                else this["_r"]["stop"](this);

            }

            else{
                ted = ted / this["time"];
                let n = "";
                for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
                if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
            }

        }

    }

}

Object.defineProperties(TweenValue.prototype, {

    isTweenValue: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween)

demo:
    const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)),
    animate = function (){
        requestAnimationFrame(animate);
        v1.update();
    }

    animate();
    v1.start();

*/
class TweenAlone extends TweenValue{

    constructor(origin, end, time, onUpdate, onEnd, onStart){
        super(origin, end, time, onUpdate, onEnd, onStart);
        
    }

    start(){
        if(this.onStart !== null) this.onStart();
        this._r = this;
        this._start();

    }

    stop(){
        this._r = null;
        
    }

}





/* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)

parameter:
attribute:
method:
    start(value: TweenValue): undefined;
    stop(value: TweenValue): undefined;

static:
    Value: TweenValue;

demo:
    //init Tween:
    const tween = new Tween(),
    animate = function (){
        requestAnimationFrame(animate);
        tween.update();
    }

    //init TweenValue:
    const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
        v2.reverse(); //v2.end 复制起始值
        return "restart"; //返回"restart"表示不删除队列, 需要继续补间
    });
    
    animate();
    tween.start(v2);

*/
class Tween extends RunningList{

    static Value = TweenValue;

    constructor(){
        super();

    }

    start(value){
        if(value.onStart !== null) value.onStart();
        if(value._r === null) this.push(value);
        value._r = this;
        value._start(this);

    }

    stop(value){
        if(value._r !== null) this.splice(value);
        value._r = null;
        
    }

}




/* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法)
parameter:    
    v1 = {x: 0}, 
    v2 = {x: 100}, 
    distance = 1,        //每次移动的距离
    onUpdate = null,    //
    onEnd = null

attribute:
    v1: Object;             //起点
    v2: Object;             //终点
    onUpdate: Function;        //
    onEnd: Function;         //

method:
    update(): undefined;                        //一般在动画循环里执行此方法
    updateAxis(): undefined;                     //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
    setDistance(distance: Number): undefined;     //设置每次移动的距离 (初始化时构造器自动调用一次)

demo:
    const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10),

    //计时器模拟动画循环函数, 每秒执行一次.update()
    timer = new Timer(() => {
        ttc.update();
        console.log('update: ', ttc.v1);

    }, 1000, Infinity);

    ttc.onEnd = v => {
        timer.stop();
        console.log('end: ', v);
    }

    timer.start();
    console.log(ttc);

*/
class TweenTarget{

    #distance = 1;
    #distancePow2 = 1;
    #axis = {};

    constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){
        this.v1 = v1;
        this.v2 = v2;
        this.onUpdate = onUpdate;
        this.onEnd = onEnd;
    
        this.setDistance(distance);
        this.updateAxis();
    }

    setDistance(v = 1){
        this.#distance = v;
        this.#distancePow2 = Math.pow(v, 2);
    }

    updateAxis(){
        var n, v, len = 0;

        for(n in this.v1){
            v = this.v2[n] - this.v1[n];
            len += v * v;
            this.#axis[n] = v;

        }

        len = Math.sqrt(len);

        if(len !== 0){
            
            for(n in this.v1) this.#axis[n] *= 1 / len;

        }
    }

    update(){
        var n, len = 0;

        for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2);

        if(len > this.#distancePow2){

            for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance;
            if(this.onUpdate !== null) this.onUpdate(this.v1);

        }

        else{

            for(n in this.v1) this.v1[n] = this.v2[n];
            if(this.onEnd !== null) this.onEnd(this.v1);
            
        }
    }

}




/* EventDispatcher 自定义事件管理器
parameter: 
attribute: 

method:
    clearEvents(eventName): undefined;             //清除eventName列表, 如果 eventName 未定义清除所有事件
    customEvents(eventName, eventParam): this;    //创建自定义事件 eventParam 可选 默认{}
    getParam(eventName): eventParam;
    trigger(eventName, callback): undefined;    //触发 (callback: 可选)
    register(eventName, callback): undefined;    //
    deleteEvent(eventName, callback): undefined; //

demo:
    const eventDispatcher = new EventDispatcher();
    eventDispatcher.customEvents("test", {name: "test"});

    eventDispatcher.register("test", eventParam => {
        console.log(eventParam) //Object{name: "test"}
    });

    eventDispatcher.trigger("test");

*/
class EventDispatcher{
    
    constructor(){
        this._eventsList = {};
        this.__eventsList = [];
        this.__trigger = "";

    }

    clearEvents(eventName){ 

        //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败");
        if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []

        else this._eventsList = {}

    }
    
    customEvents(eventName, eventParam = {}){ 
        //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败");
        if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
        //eventParam = eventParam || {}
        //if(eventParam.type === undefined) eventParam.type = eventName;
        this._eventsList[eventName] = {func: [], param: eventParam}
        return this;
    }

    getParam(eventName){
        return this._eventsList[eventName]["param"];
    }
    
    trigger(eventName, callback){
        //if(this._eventsList[eventName] === undefined) return;
        
        const obj = this._eventsList[eventName];
        var k, len = obj.func.length;

        if(len !== 0){
            if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam)

            //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理)
            this.__trigger = eventName;
            for(k = 0; k < len; k++) obj["func"][k](obj["param"]);
            this.__trigger = "";
            //触发过程结束
            
            //删除在触发过程中要删除的事件
            len = this.__eventsList.length;
            for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]);
            this.__eventsList.length = 0;
    
        }

    }
    
    register(eventName, callback){
        if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
        const obj = this._eventsList[eventName];
        //if(obj.func.includes(callback) === false) obj.func.push(callback);
        //else console.warn("EventDispatcher: 回调函数重复");
        obj.func.push(callback);
    }
    
    deleteEvent(eventName, callback){
        if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
        
        if(this.__trigger === eventName) this.__eventsList.push(callback);
        else{
            const obj = this._eventsList[eventName], i = obj.func.indexOf(callback);
            if(i !== -1) obj.func.splice(i, 1);
        }
        
    }

}




export {
    UTILS, 
    ColorRefTable, 
    Ajax, 
    IndexedDB, 
    TreeStruct, 
    Point, 
    Line, 
    Box, 
    Circle, 
    Polygon, 
    RGBColor, 
    Timer, 
    SeekPath, 
    RunningList, 
    TweenValue, 
    TweenAlone, 
    Tween, 
    TweenTarget, 
    EventDispatcher, 
    ShapeRect,
}

 

标签:类库,JavaScript,return,.#,len,实用,._,line,null
From: https://www.cnblogs.com/weihexinCode/p/16619798.html

相关文章

  • 20个既简单又实用的JavaScript小技巧
    1.滚动到页面顶部我们可以使用window.scrollTo()平滑滚动到页面顶部。const scrollToTop = () => {  window.scrollTo({ top: 0, left: 0, behavior: "sm......
  • 实用工具相关
    mindmanager破解与使用快捷键大全一、格式快捷键①Ctrl+B将字体加粗②Ctrl+U对选定文本加下划线③Ctrl+I使选定文本变成斜体④Ctrl+Shift+.增加字体大小⑤Ctrl+Sh......
  • 如何在 JavaScript 中将 JSON 转换为 CSV
    如何在JavaScript中将JSON转换为CSV下面是我们如何在JavaScript中轻松地将JSON转换为CSV:函数jsonToCsv(项目){constheader=Object.keys(items[0]);常......
  • JavaScript 在线课程
    JavaScript在线课程JavaScript(JS)是一种动态的、面向对象的、基于原型的编程语言。它是ECMAScript标准的实现。JS编程语言常用于创建网页脚本,允许客户端(终端用户......
  • 2022 年 8 月 JavaScript 新闻和更新
    2022年8月JavaScript新闻和更新向所有JavaScript崇拜者致敬!很遗憾夏天结束了,但我们准备了一份最新的JavaScript新闻摘要来让你振作起来。今天,您将熟悉我们全新......
  • # JavaScript 函数
    目录JavaScript函数函数概念函数的使用函数的使用函数的封装函数的参数形参和实参形参和实参个数不匹配问题return终止函数return只能返回一个值JavaScript函数函数概......
  • JavaScript高级程序设计(第3版) pdf
    高清扫描版下载链接:https://pan.baidu.com/s/1rWAAzlVrJLfwXEn_SWtBWw点击这里获取提取码JavaScript高级程序设计本书从最早期Netscape浏览器中的JavaScript开始讲起,直到......
  • Vue基础5个实用案例
    Vue基础5个实用案例前言本章节怼几个案例供读者小伙伴们练习,写不出东西就是写的少,多写就有思路,案例也懒得去搞CSS了,大家主要锤Vue就可以了。不废话直接上货!案例1:选择登陆......
  • 如何在 Javascript 中清空数组?
    如何在Javascript中清空数组?在使用JavaScript编程时,程序员可能需要在许多情况下将数组设为空,一个非常常见的问题是如何清空数组并删除其所有元素!顺便说一句,这是最受......
  • JavaScript 中的构造函数和新的运算符
    JavaScript中的构造函数和新的运算符ConstructorFunctionsandthenewOperator你好,我是Gibson,在这篇博客中,我们将学习构造函数和新的运算符。我们可以使用构造函......