1

I am trying to integrate the code in this answer (run snippet in question's answer to see an example) with the rest of the script below to allow the user to scroll down the sideButtons selection menu by hovering in the bottom or top areas of the selection menu. However, I'm not sure how to write the requestAnimationFrame function for it to work with the rest of the object structure or where to place it.

Attached to the sideButtons' mouseMove eventListener function are two hitTest's: 'lowerHoverBoxHitTest(x, y)' and 'upperHoverBoxHitTest(x, y)'. These detect if the top or bottom half of the selection menu is being hovered over. If so, hoverAmount should be incremented accordingly such that sideButtons is pushed up or down depending on which hoverBox is selected. However, this part - which must (I think) occur within the requestAnimationFrame function - is not working in the code above.

If it is still not clear how the animation should work please see the attached link above. It should be clear that it is not currently working properly... Any help will be much appreciated.

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);
}
<!doctype html>
<html lang="en">
<body>
 <div id='game'></div>
 <div class="styled-select">
  <select id="languageSelection"></select>
 </div>
  <script type='text/javascript' src='scaleStack.js'></script>
</body>
</html>
gman
  • 100,619
  • 31
  • 269
  • 393
Sadie LaBounty
  • 379
  • 1
  • 5
  • 23
  • So you can't use css, like ` #languageSelection { left: 0; right: 0; margin-left: auto; margin-right: auto;} ` ? – DaCh Jul 17 '17 at 10:58
  • @DaCh I can, so long as using this I can scale the select box with the rest of the window. – Sadie LaBounty Jul 17 '17 at 13:18
  • You should use css3 transform + litle javascript code to calculate the rate of transform's scale , anyway I think use a native html select input , isn't a perfect way make a custom selector or create a renderable selector i side of canvas. – Siamand Jul 19 '17 at 15:57
  • @SiamandMaroufi I agree it would probably be better to write a selector within the canvas itself, but there are over 100 options within it so this will involve writing either a scroll-on-hover or arrow-scroll mechanism which seems a little complicated for just a select menu. – Sadie LaBounty Jul 19 '17 at 16:01
  • @JonathanConnell If its a OpenSource project , send me the github link then I could do that in a few minutes, and I recommend typescript for this kind of projects – Siamand Jul 19 '17 at 16:08
  • @SiamandMaroufi I've updated the code above to demonstrate more precisely the problems I described in my comments on the other question. – Sadie LaBounty Jul 21 '17 at 16:44

2 Answers2

2

Why it's not working?

It's not about requestionAnimationFrame, but about your logic to calculate the scrolling(the scroll offset, the hitness for the hit zone).

  • You must check your logic for calculation of the upperBoxHitTest and lowerBoxHitTest.
  • Of course, your calculation inside mainloop is full of problem.
  • And you must be aware of out-of-index iteration inside your code.

Hint for your code style

You can't just copy a code snippet, do some simple replacements and hope it works properly. You should figure out how it works, its internal logic and you won't be afraid of push it forward with more complex implementation.

So I suggest you check your code again, and try to find out what wrong with your code. Until you make some progress or you actually can't work it out, then you may check out how my code works. If the later situation, you may have to read more books about logical thinking, problem analysis and methodology of programming.

Good Luck!

Altered code

code snippets iframe area of stackoverflow is really small, you should check out here instead 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>
e-cloud
  • 4,331
  • 1
  • 23
  • 37
0

move Game.render method's body into Game._render private method and call the _render method inside of the render method with 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.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);
});
<!doctype html>
<html lang="en">
<body>
 <div id='game'></div>
 <div class="styled-select">
  <select id="languageSelection"></select>
 </div>
  <script type='text/javascript' src='scaleStack.js'></script>
</body>
</html>
Siamand
  • 1,080
  • 10
  • 19