<html lang="en">
<body>
<div id='game'></div>
<div class="styled-select">
<select id="languageSelection"></select>
</div>
</body>
</html>
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);
}
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |