<div id="container">
<div id="board-container">
<div class="shine-container">
<div class="shine"></div>
</div>
<div id="board"></div>
<div class="info-container">
<button id="start-button" class="state-driven"><span class="re state-driven">RE</span>START</button>
<div class="flex"></div>
<div id="score">0</div>
</div>
</div>
<div class="controls">
<span class="keyboard"> Use keyboard arrow keys to control</span>
<span class="touch">Swipe up, down, left or right to control.</span>
</div>
</div>
<a href="https://github.com/ste-vg/snake" target="_blank" class="github-corner" aria-label="View source on Github"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
$color-background: #7ca256;
$color-background-dark: darken(#7ca256, 1%);
$color-snake: #212121;
html, body
{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: #d2e0d2;
overflow: hidden;
}
@import url('https://fonts.googleapis.com/css?family=VT323');
$tilt-amount: 10deg;
$shine-move: 40px;
$snake-lift: 30px;
h1
{
margin: 0;
}
#container
{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
text-transform: uppercase;
font-family: 'VT323', monospace;
perspective: 1000px;
#board-container
{
border-radius: 8px;
margin: 20px;
padding: 20px 20px 10px 20px;
//box-shadow: 9px 7px 30px -6px rgba(0,0,0,0.25);
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
background-color: white;
background-image: radial-gradient(farthest-corner at 10px 10px, $color-background 0%, $color-background-dark 100%);
transform: rotateX(0deg);
transform-style: preserve-3d;
transition: transform 0.3s ease, box-shadow 0.5s ease;
//overflow: hidden;
#board
{
border-radius: 5px;
border: solid 1px $color-snake;
transform: translateZ($snake-lift);
}
.shine-container
{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
overflow: hidden;
.shine
{
position: absolute;
top: -$shine-move;
left: -$shine-move;
right: -$shine-move;
bottom: -$shine-move;
background: linear-gradient(45deg, rgba(255,255,255,.75) 0%,rgba(255,255,255,0) 60%);
transition: transform 0.3s ease;
}
}
.info-container
{
height: 100%;
width: 100%;
min-height: 50px;
margin-top: 5px;
display: flex;
flex-direction: row;
//align-items: center;
align-content: center;
justify-content: space-between;
transform: translateZ(20px);
.label
{
margin: 5px 5px 0 0;
}
#score
{
font-size: 1.5em;
font-weight: 300;
padding: 10px 20px;
}
.flex
{
flex: 1;
}
}
}
&.PLAYING
{
#board-container
{
box-shadow: 0 45px 100px rgba(0, 0, 0, 0.3);
transform: translateZ(40px);
&.up
{
transform: rotateX($tilt-amount);
.shine-container .shine { transform: rotateX($tilt-amount) translateX(-$shine-move) translateZ(1px) }
}
&.down
{
transform: rotateX(-$tilt-amount);
.shine-container .shine { transform: rotateX(-$tilt-amount) translateX($shine-move) translateZ(1px) }
}
&.left
{
transform: rotateY(-$tilt-amount);
.shine-container .shine { transform: rotateY(-$tilt-amount) translateY(-$shine-move) translateZ(1px) }
}
&.right
{
transform: rotateY($tilt-amount);
.shine-container .shine { transform: rotateY($tilt-amount) translateY($shine-move) translateZ(1px) }
}
}
}
#start-button
{
font-family: inherit;
text-transform: uppercase;
font-size: 1.5em;
background-color: transparent;
color: $color-snake;
padding: 10px 20px;
//border: 2px solid $color-snake;
border: 0;
border-radius: 2px;
//margin: 10px 10px 40px 10px;
cursor: pointer;
outline: none;
&:hover
{
color: white;
// background-color: $color-background;
border-color: $color-background;
}
}
.state-driven
{
display: none;
}
&.READY
{
#start-button
{
display: block;
}
}
&.ENDED
{
#start-button
{
display: block;
}
.re
{
display: inline;
}
}
.controls
{
.keyboard{ display: inline; }
.touch{ display: none; }
@media (any-hover: none) and (any-pointer: coarse)
{
.keyboard{ display: none; }
.touch{ display: inline; }
}
}
}
@keyframes flash
{
0% { opacity: 1 }
50% { opacity: 0 }
}
#board
{
--grid-columns: 0;
--grid-rows: 0;
--grid-size: 0;
width: calc(var(--grid-size) * var(--grid-columns) * 1px);
height: calc(var(--grid-size) * var(--grid-rows) * 1px);
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
grid-template-rows: repeat(var(--grid-rows), 1fr);
grid-gap: 1px;
>div
{
background-color: transparent;
&.food, &.snake
{
box-shadow: 0px 0px 0px rgba(0,0,0,0.1);
transition: box-shadow 0.3s ease;
}
&.food
{
background-color: $color-snake;
border-radius: 50%;
margin: 1px;
}
&.snake
{
background-color: $color-snake;
&.head
{
&.up{ border-top-left-radius: 50%; border-top-right-radius: 50%;}
&.down{ border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;}
&.left{ border-top-left-radius: 50%; border-bottom-left-radius: 50%;}
&.right{ border-top-right-radius: 50%; border-bottom-right-radius: 50%;}
}
&.tail
{
&.up{ border-bottom-left-radius: 50% 100%; border-bottom-right-radius: 50% 100%;}
&.down{ border-top-left-radius: 50% 100%; border-top-right-radius: 50% 100%;}
&.right{ border-bottom-left-radius: 100% 50%; border-top-left-radius: 100% 50%;}
&.left{ border-bottom-right-radius: 100% 50%; border-top-right-radius: 100% 50%;}
}
&.turn-left
{
&.up{ border-top-right-radius: 50%;}
&.down{ border-bottom-right-radius: 50%;}
}
&.turn-right
{
&.up{ border-top-left-radius: 50%;}
&.down{ border-bottom-left-radius: 50%;}
}
&.turn-up
{
&.left{ border-bottom-left-radius: 50%;}
&.right{ border-bottom-right-radius: 50%;}
}
&.turn-down
{
&.left{ border-top-left-radius: 50%;}
&.right{ border-top-right-radius: 50%;}
}
&.dead
{
animation: flash 0.3s steps(1) infinite;
}
}
}
}
$snake-shadow: 5px;
.up #board >div
{
&.food, &.snake { box-shadow: 0px $snake-shadow 0px 0px rgba(0,0,0,0.1);}
}
.down #board >div
{
&.food, &.snake { box-shadow: 0px (-$snake-shadow) 0px 0px rgba(0,0,0,0.1);}
}
.left #board >div
{
&.food, &.snake { box-shadow: $snake-shadow 0px 0px 0px rgba(0,0,0,0.1);}
}
.right #board >div
{
&.food, &.snake { box-shadow: -$snake-shadow 0px 0px 0px rgba(0,0,0,0.1);}
}
View Compiled
// Also on Github with Webpack and better Typescript support
// https://github.com/ste-vg/snake
console.clear();
enum GAME_STATES
{
ready = 'READY',
playing = 'PLAYING',
ended = 'ENDED',
paused = 'PAUSED'
}
enum SOUND
{
move = 'move',
dead = 'dead',
collect = 'collect',
start = 'start'
}
interface Direction
{
name: string;
x: number;
y: number;
}
interface Position
{
x: number;
y: number;
}
interface SnakePart
{
position: Position;
direction: Direction;
}
interface States
{
direction: Direction;
nextDirection: Direction[];
speed: number;
game: string;
timeStamp: number;
snakeLength: number;
score: number;
}
class App
{
private game:Snake;
private score:HTMLElement;
private container:HTMLElement;
private boardContainer:HTMLElement;
private gameState:string;
constructor()
{
this.setupUI();
this.setupGame();
}
setupUI()
{
this.score = document.getElementById('score');
this.container = document.getElementById('container');
this.boardContainer = document.getElementById('board-container');
let startButton = Rx.Observable.fromEvent(document.getElementById('start-button'), 'click');
startButton.subscribe((e:MouseEvent) => { console.log('click'); this.startGame(); })
}
setupGame()
{
let board = document.getElementById('board');
this.game = new Snake(board);
this.game.score.subscribe((score:number) => this.score.innerHTML = String(score));
this.game.state.subscribe((state:string) =>
{
this.gameState = state;
this.container.setAttribute('class', state)
})
this.game.direction.subscribe((direction:string) => this.boardContainer.setAttribute('class', direction))
this.game.reset();
}
startGame()
{
if(this.gameState == GAME_STATES.ready || this.gameState == GAME_STATES.ended)
{
this.game.start();
}
}
}
class Snake
{
private SETTINGS = {
grid: {size: 10, rows: 20, columns: 28},
game: {scoreIncrement: 10},
snake: {startLength: 3, startSpeed: 300, speedIncrement: 10, minSpeed: 100, growBy: 2}
}
private DIRECTION = {
up: {name: 'up', x: 0, y: -1},
down: {name: 'down', x: 0, y: 1},
left: {name: 'left', x: -1, y: 0},
right: {name: 'right', x: 1, y: 0},
}
private states:States = {
direction: this.DIRECTION.up,
nextDirection: [this.DIRECTION.up],
speed: 0,
game: GAME_STATES.ready,
timeStamp: 0,
snakeLength: 0,
score: 0
}
//http://loov.io/jsfx
private sfxLibrary:any = {
"start":{
"Frequency":{"Start":463.2977575242697,"Slide":0.4268311992714056,"RepeatSpeed":0.6870767779635416},
"Generator":{"A":0.015696072909390766},
"Volume":{"Sustain":0.11353385475559997,"Decay":0.15242709930669884}
},
"collect1":{
"Frequency":{"Start":1183.9224793246758,"ChangeSpeed":0.12793431035602038,"ChangeAmount":4.8612434857196085},
"Volume":{"Sustain":0.011448880380128946,"Decay":0.3895997546965799,"Punch":0.4554389528366015}
},
"collect2":{
"Frequency":{"Start":1070.9337014976563,"ChangeSpeed":0.1375978771153015,"ChangeAmount":5.9409661118536246},
"Volume":{"Sustain":0.04890791064198004,"Decay":0.3415421194668815,"Punch":0.46291381941601983}
},
"dead":{
"Frequency":{"Start":194.70758491034655,"Slide":-0.011628522004559189,"ChangeSpeed":0.6591296059731018,"ChangeAmount":2.6287197798189297},
"Generator":{"Func":"noise"},
"Volume":{"Sustain":0.17655222296084297,"Decay":0.24077933399701645,"Punch":0.6485369099751499}
},
"move1":{
"Frequency":{"Start":452,"Slide":-0.04,"Min":30,"DeltaSlide":-0.05},
"Generator":{"Func":"sine","A":0.08999657142884616,"ASlide":0.3390436675524937},
"Filter":{"HP":0.10068425608105215},
"Volume":{"Sustain":0,"Decay":0.041,"Attack":0.011,"Punch":0.04,"Master":0.18}
},
"move2":{
"Frequency":{"Start":452,"Slide":-0.01,"Min":30,"DeltaSlide":-0.05},
"Generator":{"Func":"sine","A":0.08999657142884616,"ASlide":0.3390436675524937},
"Filter":{"HP":0.26,"LPResonance":0,"HPSlide":0.35,"LPSlide":0.51,"LP":1},
"Volume":{"Sustain":0.02,"Decay":0.001,"Attack":0.021,"Punch":0.05,"Master":0.18},
"Phaser":{"Offset":-0.03,"Sweep":-0.02},
"Vibrato":{"FrequencySlide":0.04,"Frequency":14.01,"Depth":0.06}
},
"move3":{
"Frequency":{"Start":452,"Slide":-0.01,"Min":30,"DeltaSlide":-0.05},
"Generator":{"Func":"sine","A":0.08999657142884616,"ASlide":0.3390436675524937},
"Filter":{"HP":0.26,"LPResonance":0,"HPSlide":0.35,"LPSlide":0.51,"LP":1},
"Volume":{"Sustain":0.02,"Decay":0.001,"Attack":0.021,"Punch":0.05,"Master":0.18},
"Phaser":{"Offset":-0.03,"Sweep":-0.02},
"Vibrato":{"FrequencySlide":0.04,"Frequency":14.01,"Depth":0.16}
},
"move4":{
"Frequency":{"Start":452,"Slide":-0.01,"Min":30,"DeltaSlide":-0.05},
"Generator":{"Func":"sine","A":0.08999657142884616,"ASlide":0.3390436675524937},
"Filter":{"HP":0.26,"LPResonance":0,"HPSlide":0.35,"LPSlide":0.51,"LP":1},
"Volume":{"Sustain":0.02,"Decay":0.001,"Attack":0.021,"Punch":0.05,"Master":0.18},
"Phaser":{"Offset":-0.03,"Sweep":-0.02},
"Vibrato":{"FrequencySlide":0.04,"Frequency":14.01,"Depth":0.27}
}
}
private player:any = jsfx.Sounds(this.sfxLibrary);
private sounds:any = {
collect: ['collect1', 'collect2'],
dead: ['dead'],
start: ['start'],
move: ['move1', 'move2', 'move3', 'move4']
}
private board:HTMLElement;
private grid:HTMLElement[] = [];
private snake:SnakePart[] = [];
private food:Position;
private touchStartPosition:Position;
// subjects
public state:Subject<string> = new Rx.Subject();
public score:Subject<number> = new Rx.Subject();
public direction:Subject<string> = new Rx.Subject();
// observables
private keyPress:Observable<any>;
private input:Input;
// subscriptions
private keyPressSubscription:Subscription;
private touchStartSubscription:Subscription;
private touchEndSubscription:Subscription;
private keyRestartSubscription:Subscription;
constructor(boardElement: HTMLElement)
{
this.board = boardElement;
// setup the game board grid
this.board.style.setProperty("--grid-size", String(this.SETTINGS.grid.size));
this.board.style.setProperty("--grid-columns", String(this.SETTINGS.grid.columns));
this.board.style.setProperty("--grid-rows", String(this.SETTINGS.grid.rows));
let count = this.SETTINGS.grid.columns * this.SETTINGS.grid.rows;
for(let i = 0; i < count; i++)
{
let sq = document.createElement("div");
this.grid.push(sq);
this.board.appendChild(sq);
}
// setup observables
this.input = new Input(document.body);
this.keyPress = Rx.Observable.fromEvent(document, "keydown")
.filter((e:KeyboardEvent) => ['arrowright', 'arrowleft', 'arrowup', 'arrowdown'].indexOf(e.key.toLowerCase()) >= 0)
.map((e:KeyboardEvent) =>
{
e.preventDefault();
return e.key.toLowerCase().replace('arrow','')
})
let onEnter = Rx.Observable.fromEvent(document, "keydown")
.filter((e:KeyboardEvent) => ['enter'].indexOf(e.key.toLowerCase()) >= 0)
this.touchStartSubscription = this.input.starts.subscribe((position:Position) => {
this.touchStartPosition = position;
})
this.touchEndSubscription = this.input.ends.subscribe((position:Position) =>
{
let hDiff = this.touchStartPosition.x - position.x;
let hDiffAbs = Math.abs(hDiff);
let vDiff = this.touchStartPosition.y - position.y;
let vDiffAbs = Math.abs(vDiff);
if(hDiffAbs > 10 || vDiffAbs > 10)
{
if(hDiffAbs > vDiffAbs)
{
if(hDiff < 0) this.setDirection(this.DIRECTION['right']);
else this.setDirection(this.DIRECTION['left']);
}
else
{
if(vDiff < 0) this.setDirection(this.DIRECTION['down']);
else this.setDirection(this.DIRECTION['up']);
}
}
})
this.keyPressSubscription = this.keyPress.subscribe((key: string) =>
{
if(this.states.game == GAME_STATES.playing)
{
this.setDirection(this.DIRECTION[key])
}
})
this.keyRestartSubscription = onEnter.subscribe(e => this.start())
}
private playSound(type:SOUND)
{
let options = this.sounds[type];
let selected = options[Math.floor(Math.random() * options.length)];
this.player[selected]();
}
private checkDirection(setDirection:Direction, newDirection:Direction):boolean
{
return setDirection.x != newDirection.x && setDirection.y != newDirection.y;
}
private setDirection(direction:Direction)
{
let queueable:boolean = false;
if(this.states.direction.name != this.states.nextDirection[0].name)
{
//if a valid move we could queue this move
if(this.states.nextDirection.length == 1 && this.checkDirection(this.states.nextDirection[0], direction))
{
queueable = true;
}
}
if(queueable && this.checkDirection(this.states.nextDirection[0], direction))
{
this.states.nextDirection.push(direction);
this.playSound(SOUND.move);
}
else if(this.checkDirection(this.states.direction, direction))
{
this.states.nextDirection = [direction];
this.playSound(SOUND.move);
}
}
public reset()
{
this.updateGameState(GAME_STATES.ready);
this.snake = []
this.states.direction = this.DIRECTION.up;
this.states.nextDirection = [this.DIRECTION.up];
this.states.snakeLength = this.SETTINGS.snake.startLength;
this.updateScore(0);
let center:Position = {x: Math.round(this.SETTINGS.grid.columns / 2), y: Math.round(this.SETTINGS.grid.rows / 2)};
for(let i = 0; i < this.states.snakeLength; i++)
{
let snakePart:SnakePart = {
position: {x: center.x, y: center.y + (i * 1)},
direction: this.DIRECTION.up
}
this.snake.unshift(snakePart);
}
this.placeFood();
this.draw();
}
private draw()
{
// reset all sqaures
for(let i = 0; i < this.grid.length; i++) this.grid[i].className = '';
// set snake squares
for(let i = 0; i < this.snake.length; i++)
{
let classes = ['snake'];
if(this.states.game == GAME_STATES.ended) classes.push('dead');
if(i == 0) classes.push('tail');
if(i == this.snake.length - 1) classes.push('head');
let snakePart = this.snake[i];
let nextSnakePart = this.snake[i + 1] ? this.snake[i + 1] : null;
if(nextSnakePart && snakePart.direction.name != nextSnakePart.direction.name)
{
classes.push('turn-' + nextSnakePart.direction.name)
}
if(i == 0 && nextSnakePart)
{
classes.push(nextSnakePart.direction.name);
}
else
{
classes.push(snakePart.direction.name);
}
let gridIndex = this.getIndexFromPosition(snakePart.position);
this.grid[gridIndex].className = classes.join(' ');
}
// set food sqaure
let foodSquare = this.grid[this.getIndexFromPosition(this.food)];
foodSquare.className = 'food';
}
private getIndexFromPosition(position:Position):number
{
return position.x + (position.y * this.SETTINGS.grid.columns);
}
private getPositionFromIndex(index:number):Position
{
let y = Math.floor(index / this.SETTINGS.grid.columns);
let x = Math.floor(index % this.SETTINGS.grid.columns);
return {x: x, y: y};
}
private eatFood()
{
this.addScore();
this.playSound(SOUND.collect);
this.states.snakeLength += this.SETTINGS.snake.growBy;
this.states.speed -= this.SETTINGS.snake.speedIncrement;
if(this.states.speed < this.SETTINGS.snake.minSpeed) this.states.speed = this.SETTINGS.snake.minSpeed;
this.placeFood();
}
private updateGameState(newState:string)
{
this.states.game = newState;
this.state.next(this.states.game);
}
private addScore()
{
this.updateScore(this.states.score + this.SETTINGS.game.scoreIncrement);
}
private updateScore(newScore:number)
{
this.states.score = newScore;
this.score.next(this.states.score);
}
private placeFood()
{
let takenSpaces: number[] = [];
for(let i = 0; i < this.snake.length; i++)
{
let index = this.getIndexFromPosition(this.snake[i].position);
takenSpaces.push(index);
}
let availableSpaces: number[] = [];
for(let i = 0; i < this.grid.length; i++)
{
if(takenSpaces.indexOf(i) < 0) availableSpaces.push(i);
}
let i = Math.floor(Math.random() * availableSpaces.length);
this.food = this.getPositionFromIndex(availableSpaces[i]);
}
private tick(timeStamp:number)
{
if(this.states.game == GAME_STATES.playing)
{
if(!this.states.timeStamp || (timeStamp - this.states.timeStamp) > this.states.speed)
{
this.states.timeStamp = timeStamp;
if(this.states.nextDirection.length > 1)
{
this.states.direction = this.states.nextDirection.shift();
}
else
{
this.states.direction = this.states.nextDirection[0];
}
this.direction.next(this.states.nextDirection[this.states.nextDirection.length - 1].name);
let snakeHead = this.snake[this.snake.length - 1];
let newPosition:Position = {
x: snakeHead.position.x + this.states.direction.x,
y: snakeHead.position.y + this.states.direction.y
}
// end the game if the new postion is out of bounds
if( newPosition.x < 0 ||
newPosition.x > this.SETTINGS.grid.columns - 1 ||
newPosition.y < 0 ||
newPosition.y > this.SETTINGS.grid.rows - 1)
{
return this.end();
}
// end the game if the new position is already taken by snake
for(let i = 0; i < this.snake.length; i++)
{
if(this.snake[i].position.x == newPosition.x && this.snake[i].position.y == newPosition.y)
{
return this.end();
}
}
// all good to proceed with new snake head
let newSnakeHead:SnakePart = {
position: newPosition,
direction: this.DIRECTION[this.states.direction.name]
}
this.snake.push(newSnakeHead);
while(this.snake.length > this.states.snakeLength)
{
this.snake.shift();
}
// check if head is on food
if(newSnakeHead.position.x == this.food.x && newSnakeHead.position.y == this.food.y)
{
this.eatFood();
}
this.draw();
}
window.requestAnimationFrame(time => this.tick(time));
}
}
public start()
{
this.reset();
this.playSound(SOUND.start);
this.states.speed = this.SETTINGS.snake.startSpeed;
this.updateGameState(GAME_STATES.playing);
this.tick(0);
window.focus();
}
private end()
{
console.warn('GAME OVER')
this.playSound(SOUND.dead);
this.updateGameState(GAME_STATES.ended);
this.direction.next('');
this.draw();
}
}
// touch & mouse input code form https://codepen.io/HunorMarton/post/handling-complex-mouse-and-touch-events-with-rxjs
class Input
{
private mouseDowns:Observable<Position>;
private mouseMoves:Observable<Position>;
private mouseUps:Observable<Position>;
private touchStarts:Observable<Position>;
private touchMoves:Observable<Position>;
private touchEnds:Observable<Position>;
public starts:Observable<Position>;
public moves:Observable<Position>;
public ends:Observable<Position>;
constructor(element:HTMLElement)
{
this.mouseDowns = Rx.Observable.fromEvent(element, "mousedown").map(this.mouseEventToCoordinate);
this.mouseMoves = Rx.Observable.fromEvent(window, "mousemove").map(this.mouseEventToCoordinate);
this.mouseUps = Rx.Observable.fromEvent(window, "mouseup").map(this.mouseEventToCoordinate);
this.touchStarts = Rx.Observable.fromEvent(element, "touchstart").map(this.touchEventToCoordinate);
this.touchMoves = Rx.Observable.fromEvent(element, "touchmove").map(this.touchEventToCoordinate);
this.touchEnds = Rx.Observable.fromEvent(window, "touchend").map(this.touchEventToCoordinate);
this.starts = this.mouseDowns.merge(this.touchStarts);
this.moves = this.mouseMoves.merge(this.touchMoves);
this.ends = this.mouseUps.merge(this.touchEnds);
}
private mouseEventToCoordinate = (mouseEvent:MouseEvent) =>
{
mouseEvent.preventDefault();
return {
x: mouseEvent.clientX,
y: mouseEvent.clientY
};
};
private touchEventToCoordinate = (touchEvent:TouchEvent) =>
{
//touchEvent.preventDefault();
return {
x: touchEvent.changedTouches[0].clientX,
y: touchEvent.changedTouches[0].clientY
};
};
}
let app = new App();
View Compiled
This Pen doesn't use any external CSS resources.