Javascript - 带有requestAnimationFrame的面向对象的Canvas游戏

时间:2017-07-12 19:08:52

标签: javascript html html5 canvas drop-down-menu

我正在尝试将the code in this answer(运行问题解答片段及其答案)与下面的其余部分进行整合,以允许用户向下滚动sideButtons选择菜单通过悬停在选择菜单的底部或顶部区域。但是,我不确定如何编写requestAnimationFrame函数来使用它来处理对象结构的其余部分或放置它的位置。

附在sideButtons' mouseMove eventListener功能有两个hitTest' {#1}}'和' lowerHoverBoxHitTest(x, y)'。这些检测选择菜单的上半部分或下半部分是否悬停在上面。如果是这样,upperHoverBoxHitTest(x, y)应相应递增,以便根据选择的hoverAmount向上或向下推sideButtons。但是,这部分 - 必须(我认为)发生在hoverBox函数中 - 在上面的代码中不起作用。

如果仍然不清楚动画应该如何工作,请参阅上面附带的链接。应该很清楚它目前没有正常工作......任何帮助将不胜感激。



requestAnimationFrame

var buttonTypeSelection = document.getElementById('languageSelection');

var initialButtonType;
var buttonRanges = {'1-10': [1,2,3,4,5,6,7,8,9,10],
                    'One to Ten': ['One','Two','Three','Four','Five',
                                   'Six','Seven','Eight','Nine','Ten'],
                    '0000-1010': ['0001','0010','0011','0100','0101',
                                  '0110','0111','1000','1001','1010']};
var buttonTypeIndex = {'1-10': 1, 'One to Ten': 2, '0000-1010': 3};
Object.keys(buttonRanges).forEach(function(buttonType) {
  buttonTypeSelection.options[buttonTypeSelection.options.length] = new Option(buttonType, buttonTypeIndex[buttonType]);
}, buttonRanges);

buttonTypeSelection.options.selectedIndex = 1; // set to page source language's code
initialButtonType=buttonRanges[Object.keys(buttonRanges)[buttonTypeSelection.options.selectedIndex]];

function Game (elementID,width,height){
	this.elementID = elementID;
	this.element   = document.getElementById(elementID);
	this.width = width;
	this.height = height;

	this.palette = {
		color1:'#fff',
		color2:'#000',
		color3:'#9F3A9B',
		color4:'#a84ea5',
		color5:'#b56ab2',
		color6:'#bf7dbd',
		color7:'#d5a8d2'
	};

	this.element.style.width = width + 'px';
	this.element.style.height= height + 'px';
	this.element.style.border='solid thin ' + this.palette.color2;
	this.element.style.display= 'block';
	//this.element.style.margin='1em auto';
	this.element.style.background=this.palette.color3;

  this.buttonType=buttonRanges[buttonTypeSelection.options[buttonTypeSelection.selectedIndex].text];

  this.hoverAmount = 0;
  this.overTypes = {none:0, lower:1, raise:2}
  this.overBox = 0;
  this.overDist = 0;

	this.initialGame();
}

Game.prototype.initialGame = function(){
	this.canvas  = document.createElement("canvas");
	this.canvas.width  =  this.width;
	this.canvas.height =  this.height;
	this.element.appendChild(this.canvas);

    this.initialSideButtons();
	this.initialTitle();
	this.initialBoard();
	this.initialFooter();

  // initial selection
  this.sideButtons.select(this.sideButtons.buttons[0]);

	this.resize(this.width,this.height);
	this.render();
	this.attachEvents();
}

Game.prototype.attachEvents = function(){
	var element = this.element;

	var getX = function(evt){return evt.offsetX || evt.layerX || (evt.clientX - element.offsetLeft);};
	var getY = function(evt){return evt.offsetY || evt.layerY || (evt.clientY - element.offsetTop);};

	var game = this;
	this.element.addEventListener('mousemove',function(evt){
		game.hover(getX(evt),getY(evt));
    if (game.sideButtons.lowerHoverBoxHitTest(game.hoverX, game.hoverY)) {
			game.overBox=game.overTypes.raise;
		} else if (game.sideButtons.upperHoverBoxHitTest(game.hoverX, game.hoverY)) {
			game.overBox=game.overTypes.lower;
		} else {
			game.overBox=game.overTypes.none;
		}
		game.render();
	});

	this.element.addEventListener('click',function(evt){
		game.sideButtons.click();
		game.render();
	});
}

Game.prototype.onSelect = function(button){
	this.selected = button;
};

Game.prototype.hover=function(x,y){
	this.hoverX = x;
	this.hoverY = y;
};

Game.prototype.initialBoard = function(){
	var game = this;
	var Board = function(){
		this.left   = 0;
		this.top    = 0;
		this.width  = 0;
		this.height = 0;
	};

	Board.prototype.render = function(ctx){
		if(game.selected){

			var shapeWidth = this.width/3;

			ctx.fillStyle = game.palette.color1;
			ctx.strokeStyle = game.palette.color1;
			var fontSize =  14;
			ctx.font = 'bold '+ fontSize +'px Noto Sans';
			ctx.textAlign='center';
			ctx.lineWidth=8;
			ctx.lineJoin = 'round';
			ctx.strokeRect(this.left + this.width/2 - (shapeWidth/2),this.height/2-(shapeWidth/2) + this.top,shapeWidth,shapeWidth);
			ctx.fillText(game.selected.text,this.left + this.width/2,this.height/2 + this.top );
		}
	};

	this.board =  new Board();
};

Game.prototype.initialSideButtons = function(){
	var game = this;
	var ButtonBar =function(text){
		this.text = text;
		this.left = 0;
		this.top  = 0;
		this.width = 1;
		this.height= 1;
		this.selected=false;
	};

	ButtonBar.prototype.hitTest=function(x,y){
		return 	(this.left < x) && (x < (this.left + this.width)) &&
				(this.top <y) && (y < (this.top + this.height));
	};

	ButtonBar.prototype.getColor=function(){
		var hovered = this.hitTest(game.hoverX,game.hoverY);

		if(this.selected){
			if(hovered)
			{
				return game.palette.color7;
			}
			return game.palette.color6;
		}

		if(hovered){
			return game.palette.color5;
		}
		return game.palette.color4;
	};

	ButtonBar.prototype.render = function(ctx){
		var fontSize = 14;
		ctx.fillStyle = this.getColor();
		ctx.fillRect(this.left, this.top, this.width, this.height);
		ctx.fillStyle = game.palette.color1;
		ctx.textAlign = 'left';
		ctx.font ='bold '+ fontSize +'px Noto Sans';
		ctx.fillText(this.text,this.left + 10,this.top+ this.height/2);
	};

	var SideButtons = function(){
		this.buttons = [];
		this.width = 1;
		this.height= 1;
		this.left=1;
		this.top=1;
	};

  SideButtons.prototype.lowerHoverBoxHitTest = function(x, y) {
    game.overDist = y - (game.title.height + game.footer.top) - game.hoverScrollSize;
    return (x >= this.width) && (x <= game.width) &&
    (y >= ((game.title.height + game.footer.top) - game.hoverScrollSize)) && (y <= (game.title.height + game.footer.top));
  }

  SideButtons.prototype.upperHoverBoxHitTest = function(x, y) {
    game.overDist = game.hoverScrollSize - y;
    return (x>=this.width) && (x <= game.width) &&
    (y >= game.title.height) && (y <= (game.title.height+game.hoverScrollSize));
  }

	SideButtons.prototype.render = function(ctx){
		if(!this.buttons.length){
			return;
		}

		var height = (this.height / this.buttons.length)/0.45;
		for(var i=0;i<this.buttons.length;i++){
			var btn = this.buttons[i];
			btn.left = this.left;
			btn.top = i * height + this.top;
			btn.width = this.width;
			btn.height = height;
			this.buttons[i].render(ctx);
		}
	};

	SideButtons.prototype.click = function() {
    var current = null;
		for(var i=0;i<this.buttons.length;i++){
			var btn = this.buttons[i];
      if(btn.hitTest(game.hoverX,game.hoverY)) {
				this.select(btn);
        break;
			}
		}
	};

  SideButtons.prototype.select = function(btn) {
    for(var i=0; i<this.buttons.length; i++) {
      this.buttons[i].selected = false;
    }
    btn.selected=true;
    game.onSelect(btn);
  };

  SideButtons.prototype.refreshShapes = function() {
    this.buttons = [];
    for (var buttonIndex=1; buttonIndex<=10; buttonIndex++) {
      this.buttons.push(new ButtonBar('Button ' + game.buttonType[buttonIndex]));
    }
  }

	this.sideButtons = new SideButtons();

  for (var buttonIndex=1; buttonIndex<=10; buttonIndex++) {
    this.sideButtons.buttons.push(new ButtonBar('Button ' + game.buttonType[buttonIndex]));
  }
};

Game.prototype.initialTitle = function(){
	var Title = function(value,width,height){
		this.value=value;
		this.width = width;
		this.height= height;
	};

	var game = this;
	Title.prototype.render=function(ctx){
		var k = 2;
		var fontSize =  this.height / k;
		ctx.fillStyle=game.palette.color1;
		ctx.fillRect(0,0,this.width,this.height);
		ctx.font='bold '+ fontSize +'px Noto Sans'; // check
		ctx.fillStyle=game.palette.color3;
		ctx.textAlign='center';
		ctx.fillText(this.value,this.width/2,this.height - fontSize/2);

	};

	this.title = new Title('Test',this.width,this.height / 10);
}

Game.prototype.initialFooter = function(){
	var Footer = function(){
		this.width = 1;
		this.height= 1;
		this.left=0;
		this.top=0;
	}
	var game = this;
	Footer.prototype.render = function(ctx){
		ctx.fillStyle =  game.palette.color5;
		ctx.fillRect(this.left,this.top,this.width,this.height);
	};

	this.footer = new Footer();
};

Game.prototype.resetCanvas = function() {
	this.canvas.width  =  this.width;
	this.canvas.height =  this.height;
};

Game.prototype.render = function () {
   var that = this;
   that._render();
}

Game.prototype._render = function() {
	this.resetCanvas();

	var context = this.canvas.getContext('2d');

    this.sideButtons.render(context);
	this.title.render(context);
	this.board.render(context);
	this.footer.render(context);

};

Game.prototype.resize =  function (width,height){
	this.width = width;
	this.height= height;

	this.element.style.width = width + 'px';
	this.element.style.height= height+ 'px';

	this.title.height = this.height / 14;
	this.title.width   = this.width;

	this.footer.height = this.title.height;
	this.footer.width  = this.width;
	this.footer.top = this.height - this.footer.height;
	this.footer.left = 0;

	this.board.top   = this.title.height;
	this.board.left  = 0;
	this.board.width = this.width / 2;
	this.board.height= this.height - this.title.height - this.footer.height;

	this.sideButtons.left= this.board.width;
	this.sideButtons.top = this.board.top + this.hoverAmount;
	this.sideButtons.width = this.width - this.board.width;
	this.sideButtons.height = this.board.height;

	this.maxSpeed = this.height*(5/500);
	this.shapeSize = this.height*(30/500);
	this.hoverScrollSize = this.height*(100/500);

	this.render();
};


var game = new Game('game',window.innerWidth -50,window.innerWidth * 2/3);

window.addEventListener('resize', function(){
	game.resize(window.innerWidth -50,window.innerWidth * 2/3);
});

buttonTypeSelection.addEventListener('change', function() {
  game.buttonType=buttonRanges[buttonTypeSelection.options[buttonTypeSelection.selectedIndex].text];
  var selectedIndex = game.sideButtons.buttons.indexOf(game.selected);
  game.sideButtons.refreshShapes();
  game.selected = game.sideButtons.buttons[selectedIndex];
  game.render();
});

requestAnimationFrame(() => {
  game.resize(window.innerWidth - 50, window.innerWidth * 2/3);
  requestAnimationFrame(mainLoop); // start main loop
});

function mainLoop() {
  if (game.overBox !== game.overTypes.none) {
    game.hoverAmount += game.overDist/game.hoverScrollSize * (game.overBox === game.overTypes.lower ? game.maxSpeed : -game.maxSpeed);
    var bottom = (game.height - (game.title.height + game.footer.height) + (game.sideButtons.buttons.length * game.shapeSize));

    // game.hoverAmount = (game.hoverAmount > 0) ? 0 : (game.hoverAmount < bottom) ? bottom : game.hoverAmount;
    game.resize(window.innerWidth - 50, window.innerWidth * 2/3);
  }
  requestAnimationFrame(mainLoop);
}
&#13;
&#13;
&#13;

2 个答案:

答案 0 :(得分:2)

为什么它不起作用?

这不是关于requestionAnimationFrame,而是关于计算滚动的逻辑(滚动偏移,命中区域的命中)。

  • 您必须检查逻辑以计算upperBoxHitTestlowerBoxHitTest
  • 当然,您在mainloop内的计算充满了问题。
  • 您必须了解代码中的索引外迭代。

提示您的代码风格

你不能只复制一个代码片段,做一些简单的替换,并希望它能正常工作。你应该弄清楚它是如何工作的,它的内部逻辑,你不会害怕通过更复杂的实现来推动它。

因此,我建议您再次检查代码,并尝试找出代码有什么问题。在你取得一些进展或实际上无法解决之前,你可以查看我的代码是如何工作的。如果是后来的情况,你可能需要阅读更多有关逻辑思维,问题分析和编程方法的书籍。

祝你好运!

更改代码

代码片段stackoverflow的iframe区域非常小,您应该在此处查看https://jsbin.com/bucisupugu/edit?js,output

const btnTypeSelectElem = document.getElementById('languageSelection');

const buttonRanges = {
    '1-10': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'One to Ten': ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten'],
    '0000-1010': ['0001', '0010', '0011', '0100', '0101', '0110', '0111', '1000', '1001', '1010']
};
const buttonTypeIndex = {
    '1-10': 1,
    'One to Ten': 2,
    '0000-1010': 3
};

Object.keys(buttonRanges)
    .forEach(function (buttonType) {
        btnTypeSelectElem.add(new Option(buttonType, buttonTypeIndex[buttonType]));
    });

btnTypeSelectElem.options.selectedIndex = 1; // set to page source language's code
const initialButtonType = buttonRanges[Object.keys(buttonRanges)[btnTypeSelectElem.options.selectedIndex]];

class Game {
    constructor(elementID, width, height) {
        this.elementID = elementID;
        this.element = document.getElementById(elementID);
        this.width = width;
        this.height = height;

        this.palette = {
            color1: '#fff',
            color2: '#000',
            color3: '#9F3A9B',
            color4: '#a84ea5',
            color5: '#b56ab2',
            color6: '#bf7dbd',
            color7: '#d5a8d2'
        };

        this.element.style.width = `${width}px`;
        this.element.style.height = `${height}px`;
        this.element.style.border = `solid thin ${this.palette.color2}`;
        this.element.style.display = 'block';
        //this.element.style.margin='1em auto';
        this.element.style.background = this.palette.color3;

        this.buttonRange = buttonRanges[btnTypeSelectElem.options[btnTypeSelectElem.selectedIndex].text];

        this.scrollTop = 0;
        this.overTypes = {
            none: 0,
            lower: 1,
            raise: 2
        };
        this.overBox = 0;

        // overDist have different meanings for upper box and lower box
        // for upper: y offset to the top of hover scroll zone
        // for lower: y offset to the bottom of hover scroll zone
        // and in fact it's actually for sidebuttons container, coz the sidebuttons is
        // the simulated scroll container
        this.overDist = 0;

        this.initiateGame();
    }

    initiateGame() {
        this.canvas = document.createElement("canvas");
        this.canvas.width = this.width;
        this.canvas.height = this.height;
        this.element.appendChild(this.canvas);

        this.initiateSideButtons();
        this.initiateTitle();
        this.initiateBoard();
        this.initiateFooter();

        // initial selection
        this.sideButtons.select(this.sideButtons.buttons[0]);

        this.resize(this.width, this.height);
        this.render();
        this.attachEvents();
    }

    attachEvents() {
        const element = this.element;

        const getX = function (evt) {
            return evt.offsetX || evt.layerX || evt.clientX - element.offsetLeft;
        };
        const getY = function (evt) {
            return evt.offsetY || evt.layerY || evt.clientY - element.offsetTop;
        };

        this.element.addEventListener('mousemove', (evt) => {
            this.hover(getX(evt), getY(evt));
            if (this.sideButtons.upperHoverBoxHitTest(this.hoverX, this.hoverY)) {
                game.overDist = game.hoverScrollZoneSize - (this.hoverY - game.title.height);
                this.overBox = this.overTypes.lower;
            } else if (this.sideButtons.lowerHoverBoxHitTest(this.hoverX, this.hoverY)) {
                game.overDist = game.hoverScrollZoneSize - (game.footer.top - this.hoverY);
                this.overBox = this.overTypes.raise;
            } else {
                game.overDist = 0
                this.overBox = this.overTypes.none;
            }
            this.render();
        });

        this.element.addEventListener('click', (evt) => {
            this.sideButtons.click();
            this.render();
        });
    }

    onSelect(button) {
        this.selected = button;
    }

    hover(x, y) {
        this.hoverX = x;
        this.hoverY = y;
    }

    initiateBoard() {
        const game = this;

        class Board {
            constructor() {
                this.left = 0;
                this.top = 0;
                this.width = 0;
                this.height = 0;
            }

            render(ctx) {
                if (game.selected) {

                    const shapeWidth = this.width / 3;

                    ctx.fillStyle = game.palette.color1;
                    ctx.strokeStyle = game.palette.color1;
                    const fontSize = 14;
                    ctx.font = `bold ${fontSize}px Noto Sans`;
                    ctx.textAlign = 'center';
                    ctx.lineWidth = 8;
                    ctx.lineJoin = 'round';
                    ctx.strokeRect(this.left + this.width / 2 - shapeWidth / 2, this.height / 2 - shapeWidth / 2 + this.top, shapeWidth, shapeWidth);
                    ctx.fillText(game.selected.text, this.left + this.width / 2, this.height / 2 + this.top);
                }
            }
        }

        this.board = new Board();
    }

    initiateSideButtons() {
        const game = this;

        class ButtonBar {
            constructor(text) {
                this.text = text;
                this.left = 0;
                this.top = 0;
                this.width = 1;
                this.height = 1;
                this.selected = false;
            }

            hitTest(x, y) {
                return this.left < x &&
                    x < this.left + this.width &&
                    this.top < y &&
                    y < this.top + this.height;
            }

            getColor() {
                const hovered = this.hitTest(game.hoverX, game.hoverY);

                if (this.selected) {
                    if (hovered) {
                        return game.palette.color7;
                    }
                    return game.palette.color6;
                }

                if (hovered) {
                    return game.palette.color5;
                }
                return game.palette.color4;
            }

            render(ctx) {
                const fontSize = 14;
                ctx.fillStyle = this.getColor();
                ctx.fillRect(this.left, this.top, this.width, this.height);
                ctx.fillStyle = game.palette.color1;
                ctx.textAlign = 'left';
                ctx.font = `bold ${fontSize}px Noto Sans`;
                ctx.fillText(this.text, this.left + 10, this.top + this.height / 2);
            }
        }

        class SideButtons {
            constructor() {
                this.buttons = [];
                this.width = 1;
                this.height = 1;
                this.left = 1;
                this.top = 1;
            }

            upperHoverBoxHitTest(x, y) {
                return x >= this.left &&
                    x <= this.left + this.width &&
                    y >= game.title.height &&
                    y <= game.title.height + game.hoverScrollZoneSize;
            }

            lowerHoverBoxHitTest(x, y) {
                return x >= this.left &&
                    x <= this.left + this.width &&
                    y >= game.footer.top - game.hoverScrollZoneSize &&
                    y <= game.footer.top;
            }

            render(ctx) {
                if (!this.buttons.length) {
                    return;
                }

                const height = this.height / this.buttons.length / 0.45;
                for (let i = 0; i < this.buttons.length; i++) {
                    const btn = this.buttons[i];
                    btn.left = this.left;
                    btn.top = i * height + this.top;
                    btn.width = this.width;
                    btn.height = height;
                    this.buttons[i].render(ctx);
                }
            }

            click() {
                const current = null;
                for (let i = 0; i < this.buttons.length; i++) {
                    const btn = this.buttons[i];
                    if (btn.hitTest(game.hoverX, game.hoverY)) {
                        this.select(btn);
                        break;
                    }
                }
            }

            select(btn) {
                for (let i = 0; i < this.buttons.length; i++) {
                    this.buttons[i].selected = false;
                }
                btn.selected = true;
                game.onSelect(btn);
            }

            refreshShapes() {
                this.buttons = [];
                // note: fix an out-of-index bug here
                for (let buttonIndex = 0; buttonIndex < 10; buttonIndex++) {
                    this.buttons.push(new ButtonBar(`Button ${game.buttonRange[buttonIndex]}`));
                }
            }
        }

        this.sideButtons = new SideButtons();

        // note: fix an out-of-index bug here
        for (let buttonIndex = 0; buttonIndex < 10; buttonIndex++) {
            this.sideButtons.buttons.push(new ButtonBar(`Button ${game.buttonRange[buttonIndex]}`));
        }
    }

    initiateTitle() {
        class Title {
            constructor(value, width, height) {
                this.value = value;
                this.width = width;
                this.height = height;
            }

            render(ctx) {
                const k = 2;
                const fontSize = this.height / k;
                ctx.fillStyle = game.palette.color1;
                ctx.fillRect(0, 0, this.width, this.height);
                ctx.font = `bold ${fontSize}px Noto Sans`; // check
                ctx.fillStyle = game.palette.color3;
                ctx.textAlign = 'center';
                ctx.fillText(this.value, this.width / 2, this.height - fontSize / 2);
            }
        }

        const game = this;

        this.title = new Title('Test', this.width, this.height / 10);
    }

    initiateFooter() {
        class Footer {
            constructor() {
                this.width = 1;
                this.height = 1;
                this.left = 0;
                this.top = 0;
            }

            render(ctx) {
                ctx.fillStyle = game.palette.color5;
                ctx.fillRect(this.left, this.top, this.width, this.height);
            }
        }

        const game = this;

        this.footer = new Footer();
    }

    resetCanvas() {
        this.canvas.width = this.width;
        this.canvas.height = this.height;
    }

    render() {
        const that = this;
        that._render();
    }

    _render() {
        this.resetCanvas();

        const context = this.canvas.getContext('2d');

        this.sideButtons.render(context);
        this.title.render(context);
        this.board.render(context);
        this.footer.render(context);
    }

    resize(width, height) {
        this.width = width;
        this.height = height;

        this.element.style.width = `${width}px`;
        this.element.style.height = `${height}px`;

        this.title.height = this.height / 14;
        this.title.width = this.width;

        this.footer.height = this.title.height;
        this.footer.width = this.width;
        this.footer.top = this.height - this.footer.height;
        this.footer.left = 0;

        this.board.top = this.title.height;
        this.board.left = 0;
        this.board.width = this.width / 2;
        this.board.height = this.height - this.title.height - this.footer.height;

        this.sideButtons.left = this.board.width;
        this.sideButtons.top = this.board.top + this.scrollTop;
        this.sideButtons.width = this.width - this.board.width;
        this.sideButtons.height = this.board.height;

        this.maxSpeed = this.height * (5 / 500);
        this.shapeSize = this.height * (30 / 500);
        // hover scroll zone is that area when mouse hovers on it will trigger scrolling behavior
        this.hoverScrollZoneSize = this.height * (100 / 500);

        this.render();
    }
}

const game = new Game('game', window.innerWidth - 50, window.innerWidth * 2 / 3);

window.addEventListener('resize', function () {
    game.resize(window.innerWidth - 50, window.innerWidth * 2 / 3);
});

btnTypeSelectElem.addEventListener('change', function () {
    game.buttonRange = buttonRanges[btnTypeSelectElem.options[btnTypeSelectElem.selectedIndex].text];
    const selectedIndex = game.sideButtons.buttons.indexOf(game.selected);
    game.sideButtons.refreshShapes();
    game.selected = game.sideButtons.buttons[selectedIndex];
    game.render();
});

requestAnimationFrame(() => {
    game.resize(window.innerWidth - 50, window.innerWidth * 2 / 3);
    requestAnimationFrame(mainLoop); // start main loop
});

function mainLoop() {
    if (game.overBox !== game.overTypes.none) {
        game.scrollTop += game.overDist / game.hoverScrollZoneSize * (game.overBox === game.overTypes.lower ? game.maxSpeed : -game.maxSpeed);
        const bottom = -game.sideButtons.height;

        game.scrollTop = (game.scrollTop > 0) ? 0 : (game.scrollTop < bottom) ? bottom : game.scrollTop;
        game.resize(window.innerWidth - 50, window.innerWidth * 2 / 3);
    }
    requestAnimationFrame(mainLoop);
}
<!doctype html>
<html lang="en">

<body>
    <div id='game'></div>
    <div class="styled-select">
        <select id="languageSelection"></select>
    </div>
    <script type='text/javascript' src='game.js'></script>
</body>

</html>

答案 1 :(得分:0)

将Game.render方法的主体移动到Game._render私有方法中,并使用requestAnimationFrame调用render方法内的_render方法。

GameState
var buttonTypeSelection = document.getElementById('languageSelection');

var initialButtonType;
var buttonRanges = {'1-10': [1,2,3,4,5,6,7,8,9,10],
                    'One to Ten': ['One','Two','Three','Four','Five',
                                   'Six','Seven','Eight','Nine','Ten'],
                    '0000-1010': ['0001','0010','0011','0100','0101',
                                  '0110','0111','1000','1001','1010']};
var buttonTypeIndex = {'1-10': 1, 'One to Ten': 2, '0000-1010': 3};
Object.keys(buttonRanges).forEach(function(buttonType) {
  buttonTypeSelection.options[buttonTypeSelection.options.length] = new Option(buttonType, buttonTypeIndex[buttonType]);
}, buttonRanges);

buttonTypeSelection.options.selectedIndex = 1; // set to page source language's code
initialButtonType=buttonRanges[Object.keys(buttonRanges)[buttonTypeSelection.options.selectedIndex]];

function Game (elementID,width,height){
	this.elementID = elementID;
	this.element   = document.getElementById(elementID);
	this.width = width;
	this.height = height;

	this.palette = {
		color1:'#fff',
		color2:'#000',
		color3:'#9F3A9B',
		color4:'#a84ea5',
		color5:'#b56ab2',
		color6:'#bf7dbd',
		color7:'#d5a8d2'
	};

	this.element.style.width = width + 'px';
	this.element.style.height= height + 'px';
	this.element.style.border='solid thin ' + this.palette.color2;
	this.element.style.display= 'block';
	//this.element.style.margin='1em auto';
	this.element.style.background=this.palette.color3;


	this.initialGame();
}

Game.prototype.initialGame = function(){
	this.canvas  = document.createElement("canvas");
	this.canvas.width  =  this.width;
	this.canvas.height =  this.height;
	this.element.appendChild(this.canvas);

	this.initialTitle();
	this.initialSideButtons();
	this.initialBoard();
	this.initialFooter();

    // initial selection
    this.sideButtons.select(this.sideButtons.buttons[0]);

	this.resize(this.width,this.height);
	this.render();
	this.attachEvents();
}

Game.prototype.attachEvents = function(){
	var element = this.element;

	var getX = function(evt){return evt.offsetX || evt.layerX || (evt.clientX - element.offsetLeft);};
	var getY = function(evt){return evt.offsetY || evt.layerY || (evt.clientY - element.offsetTop);};

	var game = this;
	this.element.addEventListener('mousemove',function(evt){
		game.hover(getX(evt),getY(evt));
		game.render();
	});

	this.element.addEventListener('click',function(evt){
		game.sideButtons.click();
		game.render();
	});
}

Game.prototype.onSelect = function(button){
	this.selected = button;
};

Game.prototype.hover=function(x,y){
	this.hoverX = x;
	this.hoverY = y;
};

Game.prototype.initialBoard = function(){
	var game = this;
	var Board = function(){
		this.left = 0;
		this.top  = 0;
		this.width =0;
		this.height=0;
	};

	Board.prototype.render = function(ctx){
		if(game.selected){

			var shapeWidth = this.width/3;

			ctx.fillStyle = game.palette.color1;
			ctx.strokeStyle = game.palette.color1;
			var fontSize =  14;
			ctx.font = 'bold '+ fontSize +'px Noto Sans';
			ctx.textAlign='center';
			ctx.lineWidth=8;
			ctx.lineJoin = 'round';
			ctx.strokeRect(this.left + this.width/2 - (shapeWidth/2),this.height/2-(shapeWidth/2) + this.top,shapeWidth,shapeWidth);
			ctx.fillText(game.selected.text,this.left + this.width/2,this.height/2 + this.top );
		}
	};

	this.board =  new Board();
};

Game.prototype.initialSideButtons = function(){
	var game = this;
	var ButtonBar =function(text){
		this.text = text;
		this.left = 0;
		this.top  = 0;
		this.width = 1;
		this.height= 1;
		this.selected=false;
	};

	ButtonBar.prototype.hitTest=function(x,y){
		return 	(this.left < x) && (x < (this.left + this.width)) &&
				(this.top <y) && (y < (this.top + this.height));
	};

	ButtonBar.prototype.getColor=function(){
		var hovered = this.hitTest(game.hoverX,game.hoverY);

		if(this.selected){
			if(hovered)
			{
				return game.palette.color7;
			}
			return game.palette.color6;
		}

		if(hovered){
			return game.palette.color5;
		}
		return game.palette.color4;
	};

	ButtonBar.prototype.render = function(ctx){
		var fontSize = 14;
		ctx.fillStyle = this.getColor();
		ctx.fillRect(this.left,this.top,this.width,this.height);
		ctx.fillStyle = game.palette.color1;
		ctx.textAlign = 'left';
		ctx.font ='bold '+ fontSize +'px Noto Sans';
		ctx.fillText(this.text,this.left + 10,this.top+ this.height/2);
	};

	var SideButtons = function(){
		this.buttons = [];
		this.width = 1;
		this.height= 1;
		this.left=1;
		this.top=1;
	};

	SideButtons.prototype.render = function(ctx){
		if(!this.buttons.length){
			return;
		}

		var height = (this.height / this.buttons.length)/0.45;
		for(var i=0;i<this.buttons.length;i++){
			var btn = this.buttons[i];
			btn.left = this.left;
			btn.top = i * height + this.top;
			btn.width = this.width;
			btn.height = height;
			this.buttons[i].render(ctx);
		}
	};

	SideButtons.prototype.click = function(){
            var current = null;
		for(var i=0;i<this.buttons.length;i++){
			var btn = this.buttons[i];
                    if(  btn.hitTest(game.hoverX,game.hoverY))
                     {
				this.select(btn);
                            break;
			 }
		}
	};

    SideButtons.prototype.select = function(btn)
    {
       for(var i=0;i<this.buttons.length;i++)
       {
          this.buttons[i].selected = false;
       }
       btn.selected=true;
       game.onSelect(btn);
    };

	this.sideButtons = new SideButtons();

  for (var buttonNumber=1; buttonNumber<=10; buttonNumber++) {
    this.sideButtons.buttons.push(new ButtonBar('Button '+buttonNumber));
  }

};

Game.prototype.initialTitle = function(){
	var Title = function(value,width,height){
		this.value=value;
		this.width = width;
		this.height= height;
	};

	var game = this;
	Title.prototype.render=function(ctx){
		var k = 2;
		var fontSize =  this.height / k;
		ctx.fillStyle=game.palette.color1;
		ctx.fillRect(0,0,this.width,this.height);
		ctx.font='bold '+ fontSize +'px Noto Sans'; // check
		ctx.fillStyle=game.palette.color3;
		ctx.textAlign='center';
		ctx.fillText(this.value,this.width/2,this.height - fontSize/2);

	};

	this.title = new Title('Test',this.width,this.height / 10);
}

Game.prototype.initialFooter = function(){
	var Footer = function(){
		this.width = 1;
		this.height= 1;
		this.left=0;
		this.top=0;
	}
	var game = this;
	Footer.prototype.render = function(ctx){
		ctx.fillStyle =  game.palette.color5;
		ctx.fillRect(this.left,this.top,this.width,this.height);
	};

	this.footer = new Footer();
};

Game.prototype.resetCanvas = function(){
	this.canvas.width  =  this.width;
	this.canvas.height =  this.height;
};

Game.prototype.render = function (){
   var that = this;
   requestAnimationFrame(function(){that._render();});
}

Game.prototype._render = function(){
	this.resetCanvas();

	var context = this.canvas.getContext('2d');

	this.title.render(context);
	this.sideButtons.render(context);
	this.board.render(context);
	this.footer.render(context);

};

Game.prototype.resize =  function (width,height){
	this.width = width;
	this.height= height;

	this.element.style.width = width + 'px';
	this.element.style.height= height+ 'px';

	this.title.height = this.height / 14;
	this.title.width   = this.width;

	this.footer.height = this.title.height;
	this.footer.width  = this.width;
	this.footer.top = this.height - this.footer.height;
	this.footer.left = 0;

	this.board.top   = this.title.height;
	this.board.left  = 0;
	this.board.width = this.width / 2;
	this.board.height= this.height - this.title.height - this.footer.height;

	this.sideButtons.left= this.board.width;
	this.sideButtons.top = this.board.top;
	this.sideButtons.width = this.width - this.board.width;
	this.sideButtons.height = this.board.height;

	this.render();
};


var game = new Game('game',window.innerWidth -50,window.innerWidth * 2/3);

window.addEventListener('resize', function(){
	game.resize(window.innerWidth -50,window.innerWidth * 2/3);
});