Создадим игру, в которой реализовано уклонение от препятствий на JavaScript.
Файл index.html
<canvas id="canvas" style="background:#eeeeee"></canvas>
Файл index.js
(function(elid, width, height, speed, strength){
var canvas = document.querySelector(elid),
ctx = canvas.getContext("2d"),
pos = 0, blocks = [];
canvas.width = width; canvas.height = height;
ctx.fillStyle = "black";
var game = setInterval(function(){
if( Math.random() < strength) blocks.push([Math.random()*(width-10),-10]);
ctx.clearRect(0,0,width,height);
ctx.fillRect(pos,height-50,10,40);
for(var i = 0; i < blocks.length; i++){
ctx.fillRect(blocks[i][0],blocks[i][1],10,10);
if( blocks[i][1] > height - 60 && blocks[i][1] < height - 10 && Math.abs( pos - blocks[i][0]) < 10 ){
clearInterval(game);
alert("Game over. You have " + Math.floor(1000 * strength) + " points.");
}
if( blocks[i][1] > height - 5 ){
blocks.splice( i, 1);
i--;
} else {
blocks[i][1] += 5;
}
}
strength += 0.001;
},speed);
document.addEventListener('mousemove', function (e) {
pos = (e.pageX > 0) ? ((e.pageX < width) ? e.pageX : width-10) : 0;
}, false);
})("#canvas",400,300,33,0.05);
вторник, 19 ноября 2013 г.
Arkanoid на JavaScript
Напишем Arkanoid на JavaScript.
Особенности программы.
В нашем арканоиде угол отражения шарика зависит от того, на какую сторону ракетки он упал: при падении на левую треть мячик отлетает влево, при попадании на правую треть - вправо (в обоих случаях снаряд отражается под немного большим углом, чем при ударе о середину), при попадании в центр шарик отлетает под 45°, а его направление по оси икс не меняется.
Дается три жизни. Правда, при попадании шарика на нижнюю грань игра не ресетится, а шарик просто отскакивает и отнимается одна жизнь.
С версткой все просто. Кирпичики представлены инлайн-блоками, ракетка и снаряд имеют position: absolute.
Область, где расположены кирпичи ограничена сверху и снизу, а значит проверять соударения есть смысл только тогда, когда снаряд находится в этой области. Высота и ширина кирпичей определена заранее. Поэтому, взяв координаты шарика, мы точно сможем определить номер кирпича, в который он попал. Если его координаты X и Y, а ширина и высота кирпичей width и height, то взяв целую часть от частного X / width и Y / height мы получим в точный номер столбца и строки col и row текущей цели. Зная это, мы можем вычислить номер этого элемента:
number = row * rowLength + col.
Хорошо что querySelectorAll возвращает элементы именно в том порядке, в котором они расположены в html. Поэтому мы легко можем удалить текущий кирпич:
elements[number].className = 'removed'.
Не смотря на то, что нативный requestAnimationFrame уже реализован во всех десктопных браузерах, было решено отказаться от него в пользу setInterval.
Файл index.html
<div id="field">
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div id="paddle"></div>
<div id="ball" style="left: 154px; top: 258px;"></div>
<div id="lifesNode">3</div>
<div id="scoreNode">0</div>
</div>
Файл index.css
* {
padding: 0;
margin: 0;
}
body {
padding: 8px;
cursor: none !important;
}
#field {
width: 322px;
height: 270px;
padding-top: 30px;
border: 1px solid #ccc;
font-size: 0;
position: relative;
}
.brick,
.removed {
width: 28px;
height: 10px;
margin: 1px;
display: inline-block;
border: 1px solid;
}
.brick {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cdd), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #cdd 0%, #777 100%);
background: linear-gradient(to bottom, #cdd 0%, #777 100%);
border-color: #777 #777 #000 #000;
border-radius: 3px;
}
.brick:nth-child(2n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #ccc 0%, #777 100%);
background: linear-gradient(to bottom, #ccc 0%, #777 100%);
}
.brick:nth-child(5n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #008));
background: -webkit-linear-gradient(top, #ccc 0%, #008 100%);
background: linear-gradient(to bottom, #ccc 0%, #008 100%);
}
.brick:nth-child(3n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #080));
background: -webkit-linear-gradient(top, #ccc 0%, #080 100%);
background: linear-gradient(to bottom, #ccc 0%, #080 100%);
}
.brick:nth-child(7n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #808));
background: -webkit-linear-gradient(top, #ccc 0%, #808 100%);
background: linear-gradient(to bottom, #ccc 0%, #808 100%);
}
.brick:nth-child(11n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #880));
background: -webkit-linear-gradient(top, #ccc 0%, #880 100%);
background: linear-gradient(to bottom, #ccc 0%, #880 100%);
}
.removed {
border-color: transparent;
}
#paddle {
width: 62px;
height: 5px;
border: 1px solid #777;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cdd), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #cdd 0%, #777 100%);
background: linear-gradient(to bottom, #cdd 0%, #777 100%);
position: absolute;
left: 128px;
top: 270px;
border-radius: 3px;
}
#ball {
width: 10px;
height: 10px;
border: 1px solid #955;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdd), color-stop(100%, #500));
background: -webkit-linear-gradient(top, #fdd 0%, #500 100%);
background: linear-gradient(to bottom, #fdd 0%, #500 100%);
border-radius: 50%;
position: absolute;
}
#lifesNode,
#scoreNode {
position: absolute;
right: -85px;
font: 14px arial, helvetica, sans-serif;
border: 1px solid #ccc;
border-radius: 4px;
padding: 1px 0;
width: 75px;
text-align: center;
}
#lifesNode {
top: 20px;
}
#lifesNode:before {
content: 'lifes: ';
}
#scoreNode {
top: 50px;
}
#scoreNode:before {
content: 'score: ';
}
Файл index.js
(function (fld, pF, px, py, dx, dy, lifes, score) {
var cycle = setInterval(function () {
var bx = pF(ball.style.left = pF(ball.style.left) + dx + 'px'),
by = pF(ball.style.top = pF(ball.style.top) + dy + 'px'),
row = ((by - 30) / 14) | 0, col = (bx / 32) | 0;
if (bx < 0 && dx < 0 || bx >= 314 && dx > 0) dx *= -1;
if (bx + 6 >= px && bx + 6 <= px + 64 && by >= 259 && by <= 264) {
dy *= -1;
if (bx + 6 <= px + 21) dx = -6;
else if (bx + 6 >= px + 43) dx = 6;
else if (Math.abs(dx) == 6) dx = (dx * 2 / 3) | 0;
}
if (by < 0) dy *= -1;
if (by >= 288 && !--lifes) clearInterval(cycle), alert('Game over!');
if (by >= 288 && lifes) dy *= -1, lifesNode.innerHTML = lifes;
if (by >= 18 && by <= 100 && fld[row * 10 + col].className != 'removed') {
dy *= -1, fld[row * 10 + col].className = 'removed';
if (dx < 0 && ((bx | 0) % 32 < 10 || (bx | 0) % 32 > 22)) dx *= -1;
if (dx > 0 && (((bx + 12) | 0) % 32 < 10 || ((bx + 12) | 0) % 32 > 22)) dx *= -1;
scoreNode.innerHTML = ++score;
if (score == 50) clearInterval(cycle), alert('Victory!');
}
}, 1000 / 60);
document.addEventListener('mousemove', function (e) {
px = (e.pageX > 40) ? ((e.pageX < 290) ? e.pageX - 40 : 256) : 0;
paddle.style.left = px + 'px';
}, false);
}(field.children, parseFloat, 129, 270, -4, -4, 3, 0));
Особенности программы.
В нашем арканоиде угол отражения шарика зависит от того, на какую сторону ракетки он упал: при падении на левую треть мячик отлетает влево, при попадании на правую треть - вправо (в обоих случаях снаряд отражается под немного большим углом, чем при ударе о середину), при попадании в центр шарик отлетает под 45°, а его направление по оси икс не меняется.
Дается три жизни. Правда, при попадании шарика на нижнюю грань игра не ресетится, а шарик просто отскакивает и отнимается одна жизнь.
С версткой все просто. Кирпичики представлены инлайн-блоками, ракетка и снаряд имеют position: absolute.
Область, где расположены кирпичи ограничена сверху и снизу, а значит проверять соударения есть смысл только тогда, когда снаряд находится в этой области. Высота и ширина кирпичей определена заранее. Поэтому, взяв координаты шарика, мы точно сможем определить номер кирпича, в который он попал. Если его координаты X и Y, а ширина и высота кирпичей width и height, то взяв целую часть от частного X / width и Y / height мы получим в точный номер столбца и строки col и row текущей цели. Зная это, мы можем вычислить номер этого элемента:
number = row * rowLength + col.
Хорошо что querySelectorAll возвращает элементы именно в том порядке, в котором они расположены в html. Поэтому мы легко можем удалить текущий кирпич:
elements[number].className = 'removed'.
Не смотря на то, что нативный requestAnimationFrame уже реализован во всех десктопных браузерах, было решено отказаться от него в пользу setInterval.
Файл index.html
<div id="field">
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div class="brick"></div>
<div id="paddle"></div>
<div id="ball" style="left: 154px; top: 258px;"></div>
<div id="lifesNode">3</div>
<div id="scoreNode">0</div>
</div>
Файл index.css
* {
padding: 0;
margin: 0;
}
body {
padding: 8px;
cursor: none !important;
}
#field {
width: 322px;
height: 270px;
padding-top: 30px;
border: 1px solid #ccc;
font-size: 0;
position: relative;
}
.brick,
.removed {
width: 28px;
height: 10px;
margin: 1px;
display: inline-block;
border: 1px solid;
}
.brick {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cdd), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #cdd 0%, #777 100%);
background: linear-gradient(to bottom, #cdd 0%, #777 100%);
border-color: #777 #777 #000 #000;
border-radius: 3px;
}
.brick:nth-child(2n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #ccc 0%, #777 100%);
background: linear-gradient(to bottom, #ccc 0%, #777 100%);
}
.brick:nth-child(5n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #008));
background: -webkit-linear-gradient(top, #ccc 0%, #008 100%);
background: linear-gradient(to bottom, #ccc 0%, #008 100%);
}
.brick:nth-child(3n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #080));
background: -webkit-linear-gradient(top, #ccc 0%, #080 100%);
background: linear-gradient(to bottom, #ccc 0%, #080 100%);
}
.brick:nth-child(7n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #808));
background: -webkit-linear-gradient(top, #ccc 0%, #808 100%);
background: linear-gradient(to bottom, #ccc 0%, #808 100%);
}
.brick:nth-child(11n) {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ccc), color-stop(100%, #880));
background: -webkit-linear-gradient(top, #ccc 0%, #880 100%);
background: linear-gradient(to bottom, #ccc 0%, #880 100%);
}
.removed {
border-color: transparent;
}
#paddle {
width: 62px;
height: 5px;
border: 1px solid #777;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cdd), color-stop(100%, #777));
background: -webkit-linear-gradient(top, #cdd 0%, #777 100%);
background: linear-gradient(to bottom, #cdd 0%, #777 100%);
position: absolute;
left: 128px;
top: 270px;
border-radius: 3px;
}
#ball {
width: 10px;
height: 10px;
border: 1px solid #955;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdd), color-stop(100%, #500));
background: -webkit-linear-gradient(top, #fdd 0%, #500 100%);
background: linear-gradient(to bottom, #fdd 0%, #500 100%);
border-radius: 50%;
position: absolute;
}
#lifesNode,
#scoreNode {
position: absolute;
right: -85px;
font: 14px arial, helvetica, sans-serif;
border: 1px solid #ccc;
border-radius: 4px;
padding: 1px 0;
width: 75px;
text-align: center;
}
#lifesNode {
top: 20px;
}
#lifesNode:before {
content: 'lifes: ';
}
#scoreNode {
top: 50px;
}
#scoreNode:before {
content: 'score: ';
}
Файл index.js
(function (fld, pF, px, py, dx, dy, lifes, score) {
var cycle = setInterval(function () {
var bx = pF(ball.style.left = pF(ball.style.left) + dx + 'px'),
by = pF(ball.style.top = pF(ball.style.top) + dy + 'px'),
row = ((by - 30) / 14) | 0, col = (bx / 32) | 0;
if (bx < 0 && dx < 0 || bx >= 314 && dx > 0) dx *= -1;
if (bx + 6 >= px && bx + 6 <= px + 64 && by >= 259 && by <= 264) {
dy *= -1;
if (bx + 6 <= px + 21) dx = -6;
else if (bx + 6 >= px + 43) dx = 6;
else if (Math.abs(dx) == 6) dx = (dx * 2 / 3) | 0;
}
if (by < 0) dy *= -1;
if (by >= 288 && !--lifes) clearInterval(cycle), alert('Game over!');
if (by >= 288 && lifes) dy *= -1, lifesNode.innerHTML = lifes;
if (by >= 18 && by <= 100 && fld[row * 10 + col].className != 'removed') {
dy *= -1, fld[row * 10 + col].className = 'removed';
if (dx < 0 && ((bx | 0) % 32 < 10 || (bx | 0) % 32 > 22)) dx *= -1;
if (dx > 0 && (((bx + 12) | 0) % 32 < 10 || ((bx + 12) | 0) % 32 > 22)) dx *= -1;
scoreNode.innerHTML = ++score;
if (score == 50) clearInterval(cycle), alert('Victory!');
}
}, 1000 / 60);
document.addEventListener('mousemove', function (e) {
px = (e.pageX > 40) ? ((e.pageX < 290) ? e.pageX - 40 : 256) : 0;
paddle.style.left = px + 'px';
}, false);
}(field.children, parseFloat, 129, 270, -4, -4, 3, 0));
Пианино на JavaScript
Напишем Пианино на JavaScript.
Клавиатура классического фортепиано состоит из 88 клавиш, покрывающих диапазон от A0 (Ля суб-контр-октавы, частота звучания 27.5 Гц) до C8 (До пятой октавы, частота 4186 Гц).
Каждая октава на клавиатуре состоит из двенадцати нот:
До, До-диез, Ре, Ре-диез, Ми, Фа, Фа-диез, Соль, Соль-диез, Ля, Ля-диез/Си-бемоль, Си. Жирным выделены клавиши верхнего ряда. Они на клавиатуре обычно бывают черного цвета.
Вот так выглядит одна октава:
Просто взглянув на таблицу частот звучания нот, становится очевидной закономерность: каждая последующая октава ровно вдвое выше предыдущей. Таким образом, можно сказать, что:
Nx = N1 × 2x-1, где:
N – название ноты;
x — номер октавы (от 0 до 8);
Nx, соответственно, частота звука, соответствующая ноте N октавы x;
В формуле фигурирует N1 вместо N0 лишь потому, что часть нот суб-контр-октавы (N0) имеет частоту звучания ниже порога слышимости человеческим ухом (< 20 Hz).
Чтобы ноты получались чистыми, нам нужны достаточно точные значения частот нот контроктавы, от которой мы начинаем считать. Вот они:
C: 32.703,
С#: 34.648,
D: 36.708,
D#: 38.891,
E: 41.203,
F: 43.654,
F#: 46.249,
G: 48.999,
G#: 51.913,
A: 55,
A#: 58.27,
B: 61.735
Основываясь на этом, напишем функцию, принимающую в качестве аргумента строку с именем клавиши в виде "A4" или "C5#", и возвращающую частоту её звучания:
function play(key) {
var controctave = {
'C': 32.703,
'С#': 34.648,
'D': 36.708,
'D#': 38.891,
'E': 41.203,
'F': 43.654,
'F#': 46.249,
'G': 48.999,
'G#': 51.913,
'A': 55,
'A#': 58.27,
'B': 61.735,
},
note = key[0].toUpperCase(),
octave = parseInt(key[1]),
sharp = key[2] == '#' ? true : false;
if (sharp) {
return controctave[note + '#'] * Math.pow(2, octave-1);
} else {
return controctave[note] * Math.pow(2, octave-1);
}
}
Немного подсократим код нашей функции:
function play(key) {
var controctave = { 'C': 32.703, 'С#': 34.648, 'D': 36.708, 'D#': 38.891, 'E': 41.203, 'F': 43.654, 'F#': 46.249, 'G': 48.999, 'G#': 51.913, 'A': 55, 'A#': 58.27, 'B': 61.735};
freq = key[2] == '#' ? controctave[key[0].toUpperCase() + '#'] * Math.pow(2, (key[1]|0) - 1) : controctave[key[0].toUpperCase()] * Math.pow(2, (key[1]|0) - 1);
return freq; }
Теперь нарисуем клавиатуру.
88 клавиш клавиатуры начинаются с ноты Ля (A0).
Соответственно, цикл будет такой: в цикле рисуем по 12 клавиш, и каждую вторую, четвертую, седьмую, девятую и одиннадцатую делаем чёрной. Каждой клавише присвоим id, соответствующий ноте, которую она должна воспроизводить при нажатии.
Получится так:
var width = 1000;
var deck = document.createElement('div'),
octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'],
id = "",
keynumber = 0,
whitekeys = 0,
keys = [];
deck.style.width = width;
parent:
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
keynumber = (i * 12) + j;
if (keynumber >= 88) break parent;
keys[keynumber] = document.createElement('div');
keys[keynumber].style.border = "1px solid black";
keys[keynumber].style.position = "absolute";
id = (octave[j][1] == '#') ? octave[j] + i + 's' : octave[j] + i;
keys[keynumber].id = id;
switch(j%12) {
case 1:
case 3:
case 6:
case 8:
case 10:
keys[keynumber].style.backgroundColor = 'black';
keys[keynumber].style.left = ((width / 50 * whitekeys) - (width / 200)) + 'px';
keys[keynumber].style.width = width/100 + "px";
keys[keynumber].style.height = "200px";
keys[keynumber].style.zIndex = 10;
break;
default:
keys[keynumber].style.backgroundColor = 'white';
keys[keynumber].style.left = (width / 50 * whitekeys) + 'px';
keys[keynumber].style.width = width/50 + "px";
keys[keynumber].style.height = "300px";
whitekeys++;
}
deck.appendChild(keys[keynumber]);
}
}
document.body.appendChild(deck);
И вновь превратим применим небольшую оптимизацию кода, сделав его более компактным:
var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [];
parent: for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
if ((i * 12) + j >= 88) break parent;
div = document.createElement('div');
div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
else {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
whitekeys++;
}
document.body.appendChild(div);}}
Научим пианино издавать звуки.
Для этого нам понадобится Web Audio API, который на сей момент поддерживается только Webkit-based браузерами и Firefox.
Добавим в строку объявления глобальных переменных создание аудиоконтекста:
context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
Добавим обработчик нажатий на клавиши:
document.body.addEventListener('click', play);
Саму функцию play изменим следующим образом:
function play(e) {
var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
osc.type = "square";
osc.connect(context.destination);
osc.start(0);
setTimeout(function() {
osc.stop(0);
osc.disconnect(context.destination);
}, 1000 / 2);}
Здесь мы создали осциллятор: osc = context.createOscillator();,
установили ему необходимую частоту звучания: osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);,
установили форму сигнала: osc.type = "square"; (по умолчанию был синусоидальный) соединили его с устройством вывода звука: osc.connect(context.destination);
и дали команду начать воспроизведение: osc.start(0);.
После этого нам необходимо заставить клавишу замолчать через некоторое время (500мс), а то она так и будет противно пищать. Для этого используем osc.stop(0), завёрнутый в интервал. Обязательный элемент: osc.disconnect(context.destination); - отключаем осциллятор от устройства вывода.
В итоге у нас получился вот такой код пианино на JavaScript:
var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [],context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
parent: for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
if ((i * 12) + j >= 88) break parent;
div = document.createElement('div');
div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
else {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
whitekeys++;
}
document.body.appendChild(div);}}
document.body.addEventListener('click', play);
function play(e) {
var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
osc.type = "square";
osc.connect(context.destination);
osc.start(0);
setTimeout(function() {
osc.stop(0);
osc.disconnect(context.destination);
}, 1000 / 2);}
Клавиатура классического фортепиано состоит из 88 клавиш, покрывающих диапазон от A0 (Ля суб-контр-октавы, частота звучания 27.5 Гц) до C8 (До пятой октавы, частота 4186 Гц).
Каждая октава на клавиатуре состоит из двенадцати нот:
До, До-диез, Ре, Ре-диез, Ми, Фа, Фа-диез, Соль, Соль-диез, Ля, Ля-диез/Си-бемоль, Си. Жирным выделены клавиши верхнего ряда. Они на клавиатуре обычно бывают черного цвета.
Вот так выглядит одна октава:
Просто взглянув на таблицу частот звучания нот, становится очевидной закономерность: каждая последующая октава ровно вдвое выше предыдущей. Таким образом, можно сказать, что:
Nx = N1 × 2x-1, где:
N – название ноты;
x — номер октавы (от 0 до 8);
Nx, соответственно, частота звука, соответствующая ноте N октавы x;
В формуле фигурирует N1 вместо N0 лишь потому, что часть нот суб-контр-октавы (N0) имеет частоту звучания ниже порога слышимости человеческим ухом (< 20 Hz).
Чтобы ноты получались чистыми, нам нужны достаточно точные значения частот нот контроктавы, от которой мы начинаем считать. Вот они:
C: 32.703,
С#: 34.648,
D: 36.708,
D#: 38.891,
E: 41.203,
F: 43.654,
F#: 46.249,
G: 48.999,
G#: 51.913,
A: 55,
A#: 58.27,
B: 61.735
Основываясь на этом, напишем функцию, принимающую в качестве аргумента строку с именем клавиши в виде "A4" или "C5#", и возвращающую частоту её звучания:
function play(key) {
var controctave = {
'C': 32.703,
'С#': 34.648,
'D': 36.708,
'D#': 38.891,
'E': 41.203,
'F': 43.654,
'F#': 46.249,
'G': 48.999,
'G#': 51.913,
'A': 55,
'A#': 58.27,
'B': 61.735,
},
note = key[0].toUpperCase(),
octave = parseInt(key[1]),
sharp = key[2] == '#' ? true : false;
if (sharp) {
return controctave[note + '#'] * Math.pow(2, octave-1);
} else {
return controctave[note] * Math.pow(2, octave-1);
}
}
Немного подсократим код нашей функции:
function play(key) {
var controctave = { 'C': 32.703, 'С#': 34.648, 'D': 36.708, 'D#': 38.891, 'E': 41.203, 'F': 43.654, 'F#': 46.249, 'G': 48.999, 'G#': 51.913, 'A': 55, 'A#': 58.27, 'B': 61.735};
freq = key[2] == '#' ? controctave[key[0].toUpperCase() + '#'] * Math.pow(2, (key[1]|0) - 1) : controctave[key[0].toUpperCase()] * Math.pow(2, (key[1]|0) - 1);
return freq; }
Теперь нарисуем клавиатуру.
88 клавиш клавиатуры начинаются с ноты Ля (A0).
Соответственно, цикл будет такой: в цикле рисуем по 12 клавиш, и каждую вторую, четвертую, седьмую, девятую и одиннадцатую делаем чёрной. Каждой клавише присвоим id, соответствующий ноте, которую она должна воспроизводить при нажатии.
Получится так:
var width = 1000;
var deck = document.createElement('div'),
octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'],
id = "",
keynumber = 0,
whitekeys = 0,
keys = [];
deck.style.width = width;
parent:
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
keynumber = (i * 12) + j;
if (keynumber >= 88) break parent;
keys[keynumber] = document.createElement('div');
keys[keynumber].style.border = "1px solid black";
keys[keynumber].style.position = "absolute";
id = (octave[j][1] == '#') ? octave[j] + i + 's' : octave[j] + i;
keys[keynumber].id = id;
switch(j%12) {
case 1:
case 3:
case 6:
case 8:
case 10:
keys[keynumber].style.backgroundColor = 'black';
keys[keynumber].style.left = ((width / 50 * whitekeys) - (width / 200)) + 'px';
keys[keynumber].style.width = width/100 + "px";
keys[keynumber].style.height = "200px";
keys[keynumber].style.zIndex = 10;
break;
default:
keys[keynumber].style.backgroundColor = 'white';
keys[keynumber].style.left = (width / 50 * whitekeys) + 'px';
keys[keynumber].style.width = width/50 + "px";
keys[keynumber].style.height = "300px";
whitekeys++;
}
deck.appendChild(keys[keynumber]);
}
}
document.body.appendChild(deck);
И вновь превратим применим небольшую оптимизацию кода, сделав его более компактным:
var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [];
parent: for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
if ((i * 12) + j >= 88) break parent;
div = document.createElement('div');
div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
else {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
whitekeys++;
}
document.body.appendChild(div);}}
Научим пианино издавать звуки.
Для этого нам понадобится Web Audio API, который на сей момент поддерживается только Webkit-based браузерами и Firefox.
Добавим в строку объявления глобальных переменных создание аудиоконтекста:
context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
Добавим обработчик нажатий на клавиши:
document.body.addEventListener('click', play);
Саму функцию play изменим следующим образом:
function play(e) {
var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
osc.type = "square";
osc.connect(context.destination);
osc.start(0);
setTimeout(function() {
osc.stop(0);
osc.disconnect(context.destination);
}, 1000 / 2);}
Здесь мы создали осциллятор: osc = context.createOscillator();,
установили ему необходимую частоту звучания: osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);,
установили форму сигнала: osc.type = "square"; (по умолчанию был синусоидальный) соединили его с устройством вывода звука: osc.connect(context.destination);
и дали команду начать воспроизведение: osc.start(0);.
После этого нам необходимо заставить клавишу замолчать через некоторое время (500мс), а то она так и будет противно пищать. Для этого используем osc.stop(0), завёрнутый в интервал. Обязательный элемент: osc.disconnect(context.destination); - отключаем осциллятор от устройства вывода.
В итоге у нас получился вот такой код пианино на JavaScript:
var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [],context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
parent: for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
if ((i * 12) + j >= 88) break parent;
div = document.createElement('div');
div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
else {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
whitekeys++;
}
document.body.appendChild(div);}}
document.body.addEventListener('click', play);
function play(e) {
var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
osc.type = "square";
osc.connect(context.destination);
osc.start(0);
setTimeout(function() {
osc.stop(0);
osc.disconnect(context.destination);
}, 1000 / 2);}
Tetris на JavaScript
Напишем полноценный Tetris на JavaScript.
Особенности программы.
Tetris на JavaScript знает все фигуры тетриса.
Управление с клавиатуры:
- кнопка влево - перемещение фигуры влево,
- кнопка вправо - перемещение фигуры вправо,
- кнопка вверх - поворот фигуры по часовой стрелке,
- кнопка вниз - ускорить падение фигуры.
Во время игры скорость падания фигур постепенно увеличивается.
Очки оперативно подсчитываются.
Опишем принцип работы JavaScript-программы.
1. Все фигурки Tetris хранятся в переменной fs="1111:01|01|01|01*011|110:010|011|001*..." в виде строки.
Чтобы получить массив фигур делаем split('*').
Далее в каждой фигуре хранится от одного (для палки) до четырёх (для L, Г и пирамидки) состояния, чтобы их можно было переворачивать. Состояния разделены двоеточием ":". Соответственно, чтобы получить одно состояние, необходимо выполнить операцию split(':'). Допустим мы получили пирамидку ("010|111"). Делаем split('|') и получаем конечный двухмерный массив для одного состояния данной фигуры. "0" - это пустое пространство, его не нужно отрисовывать, а "1" означает, что нужно рисовать.
2. Всё перемещение фигур делается двумя функциями: "стереть фигуру" и "построить фигуру".
При любых перемещениях: вправо, влево, вниз или даже повороте фигуры сначала стираем текущую отображаемую фигуру, затем пытаемся построить фигуру на новом месте. Если при постройке какой-то из квадратиков влезет за край стакана или попадает на место, где уже есть заполненный квадратик от предыдущих фигур, то в этом случае стираем активную фигуру и строим ее на предыдущем месте.
3. Перемещения фигур Tetris делаются с клавиатуры. По таймеру просто вызывается функция, которая обрабатывает нажатия с клавиатуры с кодом кнопки. При каждом вызове таймаута время до вызова уменьшается.
4. Если не удалось переместить фигуру вниз - это значит, что она упёрлась в предыдущие фигуры или дно стакана. В таком случае проверяем нет ли заполненных строк и рисуем новую фигуру вверху стакана. Если вверху стакана нарисовать фигуру не удалось, то наступает Game Over и игра заканчивается.
Файл index.html
<div class="score">score: <span id="result">0</span></div>
<div id="stack">
<div data-y="0" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="1" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="2" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="3" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="4" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="5" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="6" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="7" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="8" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="9" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="10" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="11" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="12" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="13" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="14" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="15" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="16" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="17" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="18" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="19" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
</div>
Файл index.css
#stack {
width: 130px;
height: 260px;
border: solid 1px black;
border-top: 0px;
}
.brick {
width: 11px;
height: 11px;
border: solid 1px white;
background: white;
float: left;
}
.brick.on {
background: black;
}
.brick.now {
background: green;
}
Особенности программы.
Tetris на JavaScript знает все фигуры тетриса.
Управление с клавиатуры:
- кнопка влево - перемещение фигуры влево,
- кнопка вправо - перемещение фигуры вправо,
- кнопка вверх - поворот фигуры по часовой стрелке,
- кнопка вниз - ускорить падение фигуры.
Во время игры скорость падания фигур постепенно увеличивается.
Очки оперативно подсчитываются.
Опишем принцип работы JavaScript-программы.
1. Все фигурки Tetris хранятся в переменной fs="1111:01|01|01|01*011|110:010|011|001*..." в виде строки.
Чтобы получить массив фигур делаем split('*').
Далее в каждой фигуре хранится от одного (для палки) до четырёх (для L, Г и пирамидки) состояния, чтобы их можно было переворачивать. Состояния разделены двоеточием ":". Соответственно, чтобы получить одно состояние, необходимо выполнить операцию split(':'). Допустим мы получили пирамидку ("010|111"). Делаем split('|') и получаем конечный двухмерный массив для одного состояния данной фигуры. "0" - это пустое пространство, его не нужно отрисовывать, а "1" означает, что нужно рисовать.
2. Всё перемещение фигур делается двумя функциями: "стереть фигуру" и "построить фигуру".
При любых перемещениях: вправо, влево, вниз или даже повороте фигуры сначала стираем текущую отображаемую фигуру, затем пытаемся построить фигуру на новом месте. Если при постройке какой-то из квадратиков влезет за край стакана или попадает на место, где уже есть заполненный квадратик от предыдущих фигур, то в этом случае стираем активную фигуру и строим ее на предыдущем месте.
3. Перемещения фигур Tetris делаются с клавиатуры. По таймеру просто вызывается функция, которая обрабатывает нажатия с клавиатуры с кодом кнопки. При каждом вызове таймаута время до вызова уменьшается.
4. Если не удалось переместить фигуру вниз - это значит, что она упёрлась в предыдущие фигуры или дно стакана. В таком случае проверяем нет ли заполненных строк и рисуем новую фигуру вверху стакана. Если вверху стакана нарисовать фигуру не удалось, то наступает Game Over и игра заканчивается.
Файл index.html
<div class="score">score: <span id="result">0</span></div>
<div id="stack">
<div data-y="0" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="1" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="2" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="3" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="4" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="5" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="6" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="7" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="8" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="9" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="10" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="11" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="12" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="13" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="14" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="15" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="16" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="17" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="18" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
<div data-y="19" class="line">
<div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
</div>
</div>
Файл index.css
#stack {
width: 130px;
height: 260px;
border: solid 1px black;
border-top: 0px;
}
.brick {
width: 11px;
height: 11px;
border: solid 1px white;
background: white;
float: left;
}
.brick.on {
background: black;
}
.brick.now {
background: green;
}
Файл index.js
var fs = "1111:01|01|01|01*011|110:010|011|001*110|011:001|011|010*111|010:01|11|01:010|111:10|11|10*11|11*010|010|011:111|100:11|01|01:001|111*01|01|11:100|111:11|10|10:111|001", now = [3,0], pos = [4,0];
var gP = function(x,y) { return document.querySelector('[data-y="'+y+'"] [data-x="'+x+'"]'); };
var draw = function(ch, cls) {
var f = fs.split('*')[now[0]].split(':')[now[1]].split('|').map(function(a){return a.split('')});
for(var y=0; y<f.length; y++)
for(var x=0; x<f[y].length; x++)
if(f[y][x]=='1') {
if(x+pos[0]+ch[0]>9||x+pos[0]+ch[0]<0||y+pos[1]+ch[1]>19||gP(x+pos[0]+ch[0],y+pos[1]+ch[1]).classList.contains('on')) return false;
gP(x+pos[0]+ch[0], y+pos[1]+ch[1]).classList.add(cls!==undefined?cls:'now');
}
pos = [pos[0]+ch[0], pos[1]+ch[1]];
}
var deDraw = function(){ if(document.querySelectorAll('.now').length>0) deDraw(document.querySelector('.now').classList.remove('now')); }
var check = function(){
for(var i=0; i<20; i++)
if(document.querySelectorAll('[data-y="'+i+'"] .brick.on').length == 10)
return check(roll(i), document.querySelector('#result').innerHTML=Math.floor(document.querySelector('#result').innerHTML)+10);
};
var roll = function(ln){ if(false !== (document.querySelector('[data-y="'+ln+'"]').innerHTML = document.querySelector('[data-y="'+(ln-1)+'"]').innerHTML) && ln>1) roll(ln-1); };
window.addEventListener('keydown', kdf = function(e){
if(e.keyCode==38&&false!==(now[1]=((prv=now[1])+1)%fs.split('*')[now[0]].split(':').length) && false===draw([0,0], undefined, deDraw())) draw([0,0],undefined, deDraw(), now=[now[0],prv]);
if((e.keyCode==39||e.keyCode==37)&&false===draw([e.keyCode==39?1:-1,0],undefined,deDraw())) draw([0,0],undefined,deDraw());
if(e.keyCode == 40)
if(false === draw([0,1], undefined, deDraw())) {
if(draw([0,0], 'on', deDraw())||true) check();
if(false === draw([0,0], undefined, now = [Math.floor(Math.random()*fs.split('*').length),0], pos = [4,0])) {
toV=-1;
alert('Your score: '+document.querySelector('#result').innerHTML);
}
}
});
toF = function() {
kdf({keyCode:40});
setTimeout(function(){if(toV>=0)toF();}, toV=toV>0?toV-0.5:toV);
}
toF(toV = 500);
среда, 13 ноября 2013 г.
Express JS Basic Authorizaton
Как сделать свою Basic Authorizaton в Express JS?
В Express.JS есть встроенный middleware для авторизации. Если необходимо заблокировать доступ к вашему приложению, то для этого нужно добавить всего лишь одну:
app.use(express.basicAuth('username', 'password'));
Так же Express JS поддерживает альтернативный вариант с вызовом функции-коллбэка:
app.use(express.basicAuth(function(user, pass, next) {
var result = (user === 'testUser' && pass === 'testPass');
next(null /* error */, result);
}));
Если нужно ограничить доступ только к определенным url'ам, то middleware basicAuth можно использовать не глобально, а только в рамках роутера:
var auth = express.basicAuth(function(user, pass, next) {
var result = (user === 'testUser' && pass === 'testPass');
next(null, result);
});
app.get('/home', auth, function(req, res) {
res.send('Hello World');
});
basicAuth достался Express JS в наследство от connect. Она устроена достаточно просто.
Когда браузер запрашивает страницу у сервера, то сервер сообщает браузеру о необходимости авторизации с помощью заголовка WWW-Authenticate:
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
res.end('Unauthorized');
В ответ на это, браузер показывает пользователю форму ввода логина и пароля,после ввода которых отправляет повторный запрос к серверу.
На этот раз запрос уже будет содержать заголовок:
Authorization:Basic dXNlcm5hbWU6cGFzc3dvcmQ=, где dXNlcm5hbWU6cGFzc3dvcmQ= - это строка 'username:password'.
В этом легко можно убедиться, если на сервере выполнить такую команду:
var str = new Buffer('dXNlcm5hbWU6cGFzc3dvcmQ=', 'base64').toString();
console.log(str); // выведет 'username:password'
Теперь браузер будет прикреплять эту строку к каждому запросу, а basicAuth будет проверять верные ли данные пользователя указаны в запросе.
В Express.JS есть встроенный middleware для авторизации. Если необходимо заблокировать доступ к вашему приложению, то для этого нужно добавить всего лишь одну:
app.use(express.basicAuth('username', 'password'));
Так же Express JS поддерживает альтернативный вариант с вызовом функции-коллбэка:
app.use(express.basicAuth(function(user, pass, next) {
var result = (user === 'testUser' && pass === 'testPass');
next(null /* error */, result);
}));
Если нужно ограничить доступ только к определенным url'ам, то middleware basicAuth можно использовать не глобально, а только в рамках роутера:
var auth = express.basicAuth(function(user, pass, next) {
var result = (user === 'testUser' && pass === 'testPass');
next(null, result);
});
app.get('/home', auth, function(req, res) {
res.send('Hello World');
});
basicAuth достался Express JS в наследство от connect. Она устроена достаточно просто.
Когда браузер запрашивает страницу у сервера, то сервер сообщает браузеру о необходимости авторизации с помощью заголовка WWW-Authenticate:
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
res.end('Unauthorized');
В ответ на это, браузер показывает пользователю форму ввода логина и пароля,после ввода которых отправляет повторный запрос к серверу.
На этот раз запрос уже будет содержать заголовок:
Authorization:Basic dXNlcm5hbWU6cGFzc3dvcmQ=, где dXNlcm5hbWU6cGFzc3dvcmQ= - это строка 'username:password'.
В этом легко можно убедиться, если на сервере выполнить такую команду:
var str = new Buffer('dXNlcm5hbWU6cGFzc3dvcmQ=', 'base64').toString();
console.log(str); // выведет 'username:password'
Теперь браузер будет прикреплять эту строку к каждому запросу, а basicAuth будет проверять верные ли данные пользователя указаны в запросе.
пятница, 8 ноября 2013 г.
JavaScript Drag and Drop Более простой пример
Файл index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style/default/index.css" rel="stylesheet" type="text/css" />
<script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
<title>Drag</title>
</head>
<body>
<div id="grid">
<div id="one"></div>
<div class="two"></div>
<div class="two"></div>
</div>
</body>
</html>
Файл index.css
/*****************************************************************\
Drag
\*****************************************************************/
div#grid {
position: relative;
margin-top: 100px;
margin-left: 100px;
border: 1px solid black;
}
div#one {
width: 100px;
height: 100px;
background-color: green;
}
div.two {
width: 300px;
height: 300px;
margin-top: 200px;
margin-left: 200px;
background-color: blue;
}
.drag {
z-index: 100;
position: absolute;
background-color: red !important;
cursor: move !important;
}
Файл index.js
require.config({
paths: {
jquery: 'lib/jquery/jquery'
}
});
require(
[
'jquery'
]
, function(
$
) {
var globalDraggingElement = null;
$('div#one').mousedown(function(event){
globalDraggingElement = $(this);
startDragElement(event);
event.preventDefault();
});
$(document).mousemove(function(event){
if (globalDraggingElement) {
moveElement(event);
}
event.preventDefault();
});
$(document).mouseup(function(event){
if (globalDraggingElement) {
dropElement(event);
}
});
var globalMouseCursorOffset = null;
// Начинаем перетаскивать элемент
function startDragElement (event) {
if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
globalDraggingElement = null;
return;
}
globalMouseCursorOffset = {
x: event.pageX - globalDraggingElement.position().left
, y: event.pageY - globalDraggingElement.position().top
};
globalDraggingElement.addClass('drag');
}
// Передвигаем перетаскиваемый элемент
function moveElement (event) {
globalDraggingElement.css({
left: event.pageX - globalMouseCursorOffset.x
, top: event.pageY - globalMouseCursorOffset.y
});
}
// Сбрасываем перетаскиваемый элемент в нужное место
function dropElement (event) {
// Вставка перетаскиваемого элемента
var dropTargetElements = $('div.two')
, dropTargetElementsLength = dropTargetElements.length
, dropTargetElement
, i;
for (i = 0; i < dropTargetElementsLength; i++) {
dropTargetElement = dropTargetElements.eq(i);
if (
(event.pageX > dropTargetElement.offset().left)
&& (event.pageX < dropTargetElement.offset().left + dropTargetElement.width())
&& (event.pageY > dropTargetElement.offset().top)
&& (event.pageY < dropTargetElement.offset().top + dropTargetElement.height())
) {
dropTargetElement.append(globalDraggingElement);
// Запуск функции после сброса перетаскиваемого элемента
dropResult();
}
}
// Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
globalDraggingElement.removeClass('drag');
globalDraggingElement = null;
}
function dropResult () {
console.log('Element dropped.');
}
}
);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style/default/index.css" rel="stylesheet" type="text/css" />
<script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
<title>Drag</title>
</head>
<body>
<div id="grid">
<div id="one"></div>
<div class="two"></div>
<div class="two"></div>
</div>
</body>
</html>
Файл index.css
/*****************************************************************\
Drag
\*****************************************************************/
div#grid {
position: relative;
margin-top: 100px;
margin-left: 100px;
border: 1px solid black;
}
div#one {
width: 100px;
height: 100px;
background-color: green;
}
div.two {
width: 300px;
height: 300px;
margin-top: 200px;
margin-left: 200px;
background-color: blue;
}
.drag {
z-index: 100;
position: absolute;
background-color: red !important;
cursor: move !important;
}
Файл index.js
require.config({
paths: {
jquery: 'lib/jquery/jquery'
}
});
require(
[
'jquery'
]
, function(
$
) {
var globalDraggingElement = null;
$('div#one').mousedown(function(event){
globalDraggingElement = $(this);
startDragElement(event);
event.preventDefault();
});
$(document).mousemove(function(event){
if (globalDraggingElement) {
moveElement(event);
}
event.preventDefault();
});
$(document).mouseup(function(event){
if (globalDraggingElement) {
dropElement(event);
}
});
var globalMouseCursorOffset = null;
// Начинаем перетаскивать элемент
function startDragElement (event) {
if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
globalDraggingElement = null;
return;
}
globalMouseCursorOffset = {
x: event.pageX - globalDraggingElement.position().left
, y: event.pageY - globalDraggingElement.position().top
};
globalDraggingElement.addClass('drag');
}
// Передвигаем перетаскиваемый элемент
function moveElement (event) {
globalDraggingElement.css({
left: event.pageX - globalMouseCursorOffset.x
, top: event.pageY - globalMouseCursorOffset.y
});
}
// Сбрасываем перетаскиваемый элемент в нужное место
function dropElement (event) {
// Вставка перетаскиваемого элемента
var dropTargetElements = $('div.two')
, dropTargetElementsLength = dropTargetElements.length
, dropTargetElement
, i;
for (i = 0; i < dropTargetElementsLength; i++) {
dropTargetElement = dropTargetElements.eq(i);
if (
(event.pageX > dropTargetElement.offset().left)
&& (event.pageX < dropTargetElement.offset().left + dropTargetElement.width())
&& (event.pageY > dropTargetElement.offset().top)
&& (event.pageY < dropTargetElement.offset().top + dropTargetElement.height())
) {
dropTargetElement.append(globalDraggingElement);
// Запуск функции после сброса перетаскиваемого элемента
dropResult();
}
}
// Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
globalDraggingElement.removeClass('drag');
globalDraggingElement = null;
}
function dropResult () {
console.log('Element dropped.');
}
}
);
JavaScript Drag and Drop
Файл index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style/default/index.css" rel="stylesheet" type="text/css" />
<script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
<title>Drag</title>
</head>
<body>
<div id="grid">
<div id="one"></div>
<div class="two"></div>
<div class="two"></div>
</div>
</body>
</html>
Файл index.css
/*****************************************************************\
Drag
\*****************************************************************/
div#grid {
position: relative;
margin-top: 100px;
margin-left: 100px;
border: 1px solid black;
}
div#one {
width: 100px;
height: 100px;
background-color: green;
}
div.two {
width: 300px;
height: 300px;
margin-top: 200px;
margin-left: 200px;
background-color: blue;
}
.drag {
z-index: 100;
position: absolute;
background-color: #50876b;
color: #ffffff;
cursor: move !important;
}
Файл index.js
require.config({
paths: {
jquery: 'lib/jquery/jquery'
}
});
require(
[
'jquery'
]
, function(
$
) {
var globalDraggingElement = null;
$('div#one').mousedown(function(event){
globalDraggingElement = $(this);
startDragElement(event);
event.preventDefault();
});
$(document).mousemove(function(event){
if (globalDraggingElement) {
moveElement(event);
}
event.preventDefault();
});
$(document).mouseup(function(){
if (globalDraggingElement) {
dropElement();
}
});
var globalDraggingElementOriginalPosition = null
, globalStartDragMouseCursorPosition = null;
// Начинаем перетаскивать элемент
function startDragElement (event) {
if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
globalDraggingElement = null;
return;
}
globalDraggingElementOriginalPosition = globalDraggingElement.position();
globalStartDragMouseCursorPosition = {
left: event.pageX
, top: event.pageY
};
console.log('Original Position X: ' + globalDraggingElementOriginalPosition.left
+ ' | Original Position Y: ' + globalDraggingElementOriginalPosition.top);
console.log('Cursor Start Position X: ' + globalStartDragMouseCursorPosition.left
+ ' | Cursor Start Position Y: ' + globalStartDragMouseCursorPosition.top);
globalDraggingElement.addClass('drag');
}
var globalCurrentPositionOfDraggingElement = null
, globalResultPositionOfDraggingElement = null
, globalDropTargetElement = null;
// Передвигаем перетаскиваемый элемент
function moveElement (event) {
globalCurrentPositionOfDraggingElement = {
left: event.pageX - globalStartDragMouseCursorPosition.left
, top: event.pageY - globalStartDragMouseCursorPosition.top
};
console.log('CurrentPositionOfDraggingElement X: ' + globalCurrentPositionOfDraggingElement.left
+ ' | CurrentPositionOfDraggingElement Y: ' + globalCurrentPositionOfDraggingElement.top);
globalResultPositionOfDraggingElement = {
left: globalDraggingElementOriginalPosition.left + globalCurrentPositionOfDraggingElement.left
, top: globalDraggingElementOriginalPosition.top + globalCurrentPositionOfDraggingElement.top
};
console.log('ResultPositionOfDraggingElement X: ' + globalResultPositionOfDraggingElement.left
+ ' | ResultPositionOfDraggingElement Y: ' + globalResultPositionOfDraggingElement.top);
globalDraggingElement.css({
left: globalResultPositionOfDraggingElement.left
, top: globalResultPositionOfDraggingElement.top
});
globalDropTargetElement = findDropTargetElement (globalResultPositionOfDraggingElement.left, globalResultPositionOfDraggingElement.top);
}
// Определяем над каким элементом находится перетаскиваемый элемент
function findDropTargetElement (x, y) {
var dropTargetElements = $('div.two')
, dropTargetElementsLength = dropTargetElements.length
, dropTargetElement
, dropTargetElementX
, dropTargetElementY
, dropTargetElementWidth
, dropTargetElementHeight
, i;
for (i = 0; i < dropTargetElementsLength; i++) {
dropTargetElement = dropTargetElements.eq(i);
dropTargetElementX = dropTargetElement.offset().left;
dropTargetElementY = dropTargetElement.offset().top;
dropTargetElementWidth = dropTargetElement.width() / 2;
dropTargetElementHeight = dropTargetElement.height() / 2;
if ( (x > (dropTargetElementX - dropTargetElementWidth)) && (y < (dropTargetElementX + dropTargetElementWidth))
|| (y > (dropTargetElementY - dropTargetElementHeight)) && (y < (dropTargetElementY + dropTargetElementHeight))
) {
return dropTargetElement;
}
}
return null;
}
// Сбрасываем перетаскиваемый элемент в нужное место
function dropElement () {
// Вставка перетаскиваемого элемента
if (globalDropTargetElement) {
globalDropTargetElement.append(globalDraggingElement);
}
// Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
globalDraggingElement.removeClass('drag');
globalDraggingElement = null;
// Запуск функции после сброса перетаскиваемого элемента
dropResult();
}
function dropResult () {
console.log('Element dropped.');
}
}
);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style/default/index.css" rel="stylesheet" type="text/css" />
<script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
<title>Drag</title>
</head>
<body>
<div id="grid">
<div id="one"></div>
<div class="two"></div>
<div class="two"></div>
</div>
</body>
</html>
Файл index.css
/*****************************************************************\
Drag
\*****************************************************************/
div#grid {
position: relative;
margin-top: 100px;
margin-left: 100px;
border: 1px solid black;
}
div#one {
width: 100px;
height: 100px;
background-color: green;
}
div.two {
width: 300px;
height: 300px;
margin-top: 200px;
margin-left: 200px;
background-color: blue;
}
.drag {
z-index: 100;
position: absolute;
background-color: #50876b;
color: #ffffff;
cursor: move !important;
}
Файл index.js
require.config({
paths: {
jquery: 'lib/jquery/jquery'
}
});
require(
[
'jquery'
]
, function(
$
) {
var globalDraggingElement = null;
$('div#one').mousedown(function(event){
globalDraggingElement = $(this);
startDragElement(event);
event.preventDefault();
});
$(document).mousemove(function(event){
if (globalDraggingElement) {
moveElement(event);
}
event.preventDefault();
});
$(document).mouseup(function(){
if (globalDraggingElement) {
dropElement();
}
});
var globalDraggingElementOriginalPosition = null
, globalStartDragMouseCursorPosition = null;
// Начинаем перетаскивать элемент
function startDragElement (event) {
if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
globalDraggingElement = null;
return;
}
globalDraggingElementOriginalPosition = globalDraggingElement.position();
globalStartDragMouseCursorPosition = {
left: event.pageX
, top: event.pageY
};
console.log('Original Position X: ' + globalDraggingElementOriginalPosition.left
+ ' | Original Position Y: ' + globalDraggingElementOriginalPosition.top);
console.log('Cursor Start Position X: ' + globalStartDragMouseCursorPosition.left
+ ' | Cursor Start Position Y: ' + globalStartDragMouseCursorPosition.top);
globalDraggingElement.addClass('drag');
}
var globalCurrentPositionOfDraggingElement = null
, globalResultPositionOfDraggingElement = null
, globalDropTargetElement = null;
// Передвигаем перетаскиваемый элемент
function moveElement (event) {
globalCurrentPositionOfDraggingElement = {
left: event.pageX - globalStartDragMouseCursorPosition.left
, top: event.pageY - globalStartDragMouseCursorPosition.top
};
console.log('CurrentPositionOfDraggingElement X: ' + globalCurrentPositionOfDraggingElement.left
+ ' | CurrentPositionOfDraggingElement Y: ' + globalCurrentPositionOfDraggingElement.top);
globalResultPositionOfDraggingElement = {
left: globalDraggingElementOriginalPosition.left + globalCurrentPositionOfDraggingElement.left
, top: globalDraggingElementOriginalPosition.top + globalCurrentPositionOfDraggingElement.top
};
console.log('ResultPositionOfDraggingElement X: ' + globalResultPositionOfDraggingElement.left
+ ' | ResultPositionOfDraggingElement Y: ' + globalResultPositionOfDraggingElement.top);
globalDraggingElement.css({
left: globalResultPositionOfDraggingElement.left
, top: globalResultPositionOfDraggingElement.top
});
globalDropTargetElement = findDropTargetElement (globalResultPositionOfDraggingElement.left, globalResultPositionOfDraggingElement.top);
}
// Определяем над каким элементом находится перетаскиваемый элемент
function findDropTargetElement (x, y) {
var dropTargetElements = $('div.two')
, dropTargetElementsLength = dropTargetElements.length
, dropTargetElement
, dropTargetElementX
, dropTargetElementY
, dropTargetElementWidth
, dropTargetElementHeight
, i;
for (i = 0; i < dropTargetElementsLength; i++) {
dropTargetElement = dropTargetElements.eq(i);
dropTargetElementX = dropTargetElement.offset().left;
dropTargetElementY = dropTargetElement.offset().top;
dropTargetElementWidth = dropTargetElement.width() / 2;
dropTargetElementHeight = dropTargetElement.height() / 2;
if ( (x > (dropTargetElementX - dropTargetElementWidth)) && (y < (dropTargetElementX + dropTargetElementWidth))
|| (y > (dropTargetElementY - dropTargetElementHeight)) && (y < (dropTargetElementY + dropTargetElementHeight))
) {
return dropTargetElement;
}
}
return null;
}
// Сбрасываем перетаскиваемый элемент в нужное место
function dropElement () {
// Вставка перетаскиваемого элемента
if (globalDropTargetElement) {
globalDropTargetElement.append(globalDraggingElement);
}
// Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
globalDraggingElement.removeClass('drag');
globalDraggingElement = null;
// Запуск функции после сброса перетаскиваемого элемента
dropResult();
}
function dropResult () {
console.log('Element dropped.');
}
}
);
Подписаться на:
Сообщения (Atom)



