我們就來學點有意思的,用幾十行代碼來實現一個高性能的抽獎小游戲.也基于此,來鞏固我們的javascript基礎,以及前端一些基本算法的應用.
由于目前已有很多方案可以實現九宮格抽獎動畫,比如使用動態active實現邊框動畫,用隨機算法和定時器設置在何處停止等等. 為了進一步提高性能,本文介紹的方法,將使用坐標法,將操作dom的成本降低,完全由js實現滑塊的路徑的計算,滑塊元素采用絕對定位,讓其脫離文檔流,避免其他元素的重繪等等,最后點擊按鈕我們會使用防抖函數來避免頻繁執行函數,造成不必要的性能損失.
為了讓大家更加熟悉dom結構,這里我就不用js動態生成了.如下html結構:
<div class="wrap">
<div class="title">圣誕抽抽樂</div>
<div class="box">
<div class="item">我愛你</div>
<div class="item">你愛我</div>
<div class="item">我不愛你</div>
<div class="item">你愛我</div>
<div class="item start">開始</div>
<div class="item">你愛我</div>
<div class="item">再見</div>
<div class="item">謝謝惠顧</div>
<div class="item">你愛我</div>
<div class="spin"></div>
</div>
</div>
復制代碼
九宮格布局我們使用flex來實現,核心代碼如下:
.box {
display: flex;
flex-wrap: wrap;
width: 300px;
height: 300px;
position: relative;
.item {
box-sizing: border-box;
width: 100px;
}
// 滑塊
.spin {
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
display: inline-block;
width: 100px;
height: 100px;
background-color: rgba(0,0,0,.2);
}
}
復制代碼
由上可知容器box采用flex布局,要想讓flex子元素換行,我們這里要設置flex-wrap: wrap;此時九宮格布局就實現了. 滑塊采用絕對定位,至于具體如何去沿著環形軌道運動,請繼續看下文介紹.
由上圖我們可以知道,一個九宮格的4條邊,可以用以上8個坐標收尾連接起來,那么我們可以基于這個規律.來生成環形坐標集合.代碼如下:
/**
* 生成n維環形坐標
* @param {number} n 維度
* @param {number} cell 單位坐標長度
*/
function generateCirclePath(n, cell) {
let arr=[]
for(let i=0; i< n; i++) {
arr.push([i*cell, 0])
}
for(let i=0; i< n-1; i++) {
arr.push([(n-1)*cell, (i+1)*cell])
}
for(let i=0; i< n-1; i++) {
arr.push([(n-i-2)*cell, (n-1)*cell])
}
for(let i=0; i< n-2; i++) {
arr.push([0, (n-i-2)*cell])
}
return arr
}
復制代碼
如果是單位坐標,那么cell為1,cell設計的目的就位為了和現實的元素相結合,我們可以手動設置單元格的寬度來實現不同大小的n維環形坐標集.
由抽獎動畫分析可知,我們滑塊運動的軌跡,其實就是環形坐標集合,所以我們只要讓滑塊的頂點(默認左上角)沿著環形坐標集合一步步變化就好了.
function run(el, path, n=1, i=0, len=path.length) {
setTimeout(()=> {
if(n > 0) {
if(len <=i) {
i=n===1 ? len : 0
n--
}
el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
run(el, path, n, ++i, len)
}
}, 300)
}
復制代碼
這樣就能實現我們的滑塊按照九宮格邊框運動的動畫了,當然以上函數只是基本的動畫, 還沒有實現在隨機位置停止, 以及滑塊的加速度運動,這塊需要一定的技巧和js基礎知識比如閉包.
加速度運動其實很簡單,比如每轉過一圈將setTimeout的延遲時間改變即可.代碼如下:
function run(el, path, n=1, speed=60, i=0, len=path.length) {
setTimeout(()=> {
if(n > 0) {
if(len <=i) {
i=n===1 ? len : 0
n--
speed +=(300 - speed) / n
}
el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
run(el, path, n, speed, ++i, len)
}
}, speed)
}
復制代碼
隨機停止這塊主要是用了Math.random這個API, 我們在最后一圈的時候, 根據隨機返回的數值來決定何時停止,這里我們在函數內部實現隨機數值,完整代碼如下:
/**
* 環形隨機軌道運動函數
* @param {element} el 運動的dom元素
* @param {array} path 運動的環形坐標集合
* @param {number} speed 運動的初始速度
* @param {number} i 運動的初始位置
* @param {number} len 路徑的長度
* @param {number} random 中獎坐標
*/
function run(el, path, n=1, speed=60, i=0, len=path.length, random=Math.floor(Math.random() * len)) {
setTimeout(()=> {
if(n > 0) {
// 如果n為1,則設置中獎數值
if(n===1) {
len=random
}
if(len <=i) {
i=n===1 ? len : 0
n--
speed +=(300 - speed) / n
}
el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
run(el, path, n, speed, ++i, len, random)
}
}, speed)
}
復制代碼
防抖函數實現:
// 防抖函數,避免頻繁點擊執行多次函數
function debounce(fn, interval=300) {
let timeout=null
return function () {
clearTimeout(timeout)
timeout=setTimeout(()=> {
fn.apply(this, arguments)
}, interval)
}
}
復制代碼
那么我們點擊時,代碼應該長這樣:
// 點擊開始按鈕,開始抽獎
$('.start').on('click',debounce(()=> { run($('.spin'), generateCirclePath(3, 100), 3) }))
復制代碼
在文章發布之后,有熱心的小伙伴們提出了幾個建議,綜合如下:
綜合以上問題,我在之前基礎上做了進一步擴展,來解決以上提到的問題.
/**
* 環形隨機軌道運動函數
* @param {element} el 運動的dom元素
* @param {array} path 運動的環形坐標集合
* @param {func} cb 動畫結束時回調
* @param {number} speed 運動的初始速度
* @param {number} i 運動的初始位置
* @param {number} len 路徑的長度
* @param {number} random 中獎坐標
*/
function run(el, path, n=1, cb, speed=60, i=0, len=path.length, random=Math.floor(Math.random() * len)) {
setTimeout(()=> {
if(n > 0) {
// 如果n為1,則設置中獎數值
if(n===1) {
len=random
}
if(len <=i) {
i=n===1 ? len : 0
n--
speed +=(300 - speed) / n
}
el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
run(el, path, n, cb, speed, ++i, len, random)
}else {
cb && cb()
}
}, speed)
}
復制代碼
// 1. 點擊開始按鈕,開始抽獎
$('.start').on('click',debounce(()=> {
// 點擊開始后禁用點擊
$('.start').css('pointer-events', 'none')
run($('.spin'), generateCirclePath(3, 100), 3, ()=> {
// 動畫結束后開啟按鈕點擊
$('.start').css('pointer-events', 'auto')
alert('抽獎結束')
})
}))
復制代碼
謝謝各位認真的建議,繼續優化吧.
該實現方式的好處是支持n維環形坐標的抽獎,基于坐標法的應用還有很多,尤其是游戲和圖形領域,在實現過程中一定要考慮性能和可擴展性,這樣我們就可以在不同場景使用同一套方法論,豈不樂哉?本文完整源碼我會放在github上,歡迎交流學習~
github地址:https://github.com/MrXujiang?tab=repositories
歡迎在公眾號《趣談前端》加入我們一起學習討論,共同探索前端的邊界。
天給大家分享了一個用javascript和HTML5做出來的音樂播放器,今天小編我要給大家做一個童年小霸王游戲機里面的經典游戲,坦克大戰,源碼全部都有,希望大家自己也能夠多練習,將知識變為自己的。
完整代碼太長,要自己練習的加這個群:594959296 所有源碼都上傳到群文件了,自助下載學習,之前也上傳了很多類似源碼,希望大家能早日成大神
學習javascript也是有門檻的,就是你的html和css至少還比較熟練,您不能連html這東東是干啥的都不知道就開始學javascript了,學乘除前,學好加減法總是有益無害的。
再說二點建議:
不要急著看一些復雜的javascript網頁特效的代碼,這樣除了打擊你的自信心,什么也學不到
看網上什么幾天精通javascript的,直接跳過吧,沒戲
覽器現在為 JavaScript 開發人員提供了許多用于創建有趣站點的選項。 Flash曾經被用來做這個 - 它很流行,無數的游戲、播放器、花哨的界面等等都是在它上面創造出來的。但是,它們不再在任何現代瀏覽器中運行。
Flash技術重量級,漏洞百出,因此開始放棄。特別是因為有 HTML5 形式的替代方案。
Canvas 是可以使用 JS 命令在其上進行繪制的畫布。它可用于創建動畫背景、各種構造函數,最重要的是,游戲。
在本文中,您將學習如何使用 JavaScript 和 HTML5 創建瀏覽器游戲。但首先,我們建議您熟悉 JS 中的面向對象編程(只需了解什么是類、方法和對象)。這是創建游戲的最佳方式,因為它允許您使用實體而不是抽象數據。但是,有一個缺點:任何版本的 Internet Explorer 都不支持 OOP。
游戲頁面布局
首先,您需要創建一個顯示畫布的頁面。這需要很少的 HTML:
<!DOCTYPE html>
<html>
<head>
<title>JS Game</title>
<link rel="stylesheet" href="style.css">
<meta charset="utf-8">
</head>
<body>
<div class="wrapper">
<canvas width="0" height="0" class="canvas" id="canvas">Your browser does not support JavaScript и HTML5 </canvas>
</div>
<script src="game.js"></script>
</body>
</html>
現在我們需要添加樣式:
body, html
{
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
}
.wrapper
{
width: 100%;
height: 100%;
}
.canvas
{
width: 100%;
height: 100%;
background: #000;
}
請注意,在 HTML 中,canvas 元素的寬度和高度為零,而 CSS 指定為 100%。 在這方面,畫布的行為就像一個圖像。 它具有實際分辨率和可見分辨率。
使用樣式更改可見分辨率。 但是,圖片的尺寸將保持不變:它只會被拉伸或壓縮。 這就是為什么稍后將通過腳本指定實際寬度和高度的原因。
游戲腳本
首先,讓我們為游戲添加一個腳本藍圖:
var canvas=document.getElementById("canvas"); //Retrieving a canvas from the DOM
var ctx=canvas.getContext("2d"); //Obtaining a context - through it you can work with the canvas
var scale=0.1; //Machine scale
Resize(); //When the page loads, the canvas size is set
window.addEventListener("resize", Resize); //Changing the size of the window will change the size of the canvas
window.addEventListener("keydown", function (e) { KeyDown(e); }); //Receiving keystrokes from the keyboard
var objects=[]; //An array of game objects
var roads=[]; //An array with backgrounds
var player=null; //The object controlled by the player - here will be the number of the object in the objects array
function Start()
{
timer=setInterval(Update, 1000 / 60); //The game state will update 60 times per second - at this rate, the update of what is happening will seem very smooth
}
function Stop()
{
clearInterval(timer); //Stopping the update
}
function Update() //Game update
{
Draw();
}
function Draw() //Working with graphics
{
ctx.clearRect(0, 0, canvas.width, canvas.height); //Clearing the canvas from the previous frame
}
function KeyDown(e)
{
switch(e.keyCode)
{
case 37: //Left
break;
case 39: //Right
break;
case 38: //Up
break;
case 40: //Down
break;
case 27: //Esc
break;
}
}
function Resize()
{
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
}
該腳本包含創建游戲所需的一切:數據(數組)、更新、繪制和控制功能。 它只剩下用基本邏輯來補充它。 也就是說,準確指定對象的行為方式以及它們在畫布上的顯示方式。
游戲邏輯
在 Update() 函數調用期間,游戲對象的狀態將發生變化。 之后,它們將使用 Draw() 函數在畫布上繪制。 所以我們實際上并沒有在畫布上移動對象,我們繪制它們一次,然后更改它們的坐標,擦除舊圖像并使用新坐標顯示對象。 這一切發生得如此之快,以至于給人一種運動的錯覺。
讓我們看一個道路的例子。
此圖像顯示在畫布上并逐漸向下移動。 緊接著,又會顯示出另一幅這樣的畫面,讓人感覺像是一條沒有盡頭的路。
為此,讓我們創建一個 Road 類:
class Road
{
constructor(image, y)
{
this.x=0;
this.y=y;
this.image=new Image();
this.image.src=image;
}
Update(road)
{
this.y +=speed; //The image shifts down when you refresh
if(this.y > window.innerHeight) //If the image has gone over the edge of the canvas, change the position
{
this.y=road.y - this.image.height + speed; //The new position is indicated with the second background
}
}
}
將 Road 類的兩個對象添加到背景數組中:
var roads=[
new Road("images/road.jpg", 0),
new Road("images/road.jpg", 626)
]; //background array
您現在可以更改 Update() 函數,以便圖像的位置隨每一幀而變化。
function Update() //Game Update
{
roads[0].Update(roads[1]);
roads[1].Update(roads[0]);
Draw();
}
只需添加這些圖像的輸出:
function Draw() //Working with graphics
{
ctx.clearRect(0, 0, canvas.width, canvas.height); //Clearing the canvas from the previous frame
for(var i=0; i < roads.length; i++)
{
ctx.drawImage
(
roads[i].image, //Render image
0, //Initial X position in the image
0, //Initial Y-axis position in the image
roads[i].image.width, //Image width
roads[i].image.height, //Image height
roads[i].x, //X-axis position on the canvas
roads[i].y, //Y-axis position on the canvas
canvas.width, //The width of the image on the canvas
canvas.width //Since the width and height of the background are the same, the width is specified as the height
);
}
}
現在你可以看到它在游戲中是如何工作的:
現在是添加玩家和 NPC 的時候了。 為此,您需要編寫一個 Car 類。 它將有一個 Move() 方法,玩家可以使用該方法控制他的汽車。 NPC 的移動將通過 Update() 完成,它只是更改 Y 坐標。
class Car
{
constructor(image, x, y)
{
this.x=x;
this.y=y;
this.image=new Image();
this.image.src=image;
}
Update()
{
this.y +=speed;
}
Move(v, d)
{
if(v=="x") //X-axis movement
{
this.x +=d; //Offset
//
if(this.x + this.image.width * scale > canvas.width)
{
this.x -=d;
}
if(this.x < 0)
{
this.x=0;
}
}
else //Y-axis movement
{
this.y +=d;
if(this.y + this.image.height * scale > canvas.height)
{
this.y -=d;
}
if(this.y < 0)
{
this.y=0;
}
}
}
}
讓我們創建第一個要檢查的對象。
var objects=[
new Car("images/car.png", 15, 10)
]; //An array of game objects
var player=0; //the number of the object controlled by the player
現在您需要向 Draw() 函數添加一個用于繪制汽車的命令。
for(var i=0; i < objects.length; i++)
{
ctx.drawImage
(
objects[i].image, //Render image
0, //Initial X position in the image
0, //Initial Y-axis position in the image
objects[i].image.width, //Image width
objects[i].image.height, //Image height
objects[i].x, //X-axis position on the canvas
objects[i].y, //Y-axis position on the canvas
objects[i].image.width * scale, //The width of the image on the canvas multiplied by the scale
objects[i].image.height * scale //The height of the image on the canvas multiplied by the scale
);
}
在按下鍵盤時調用的 KeyDown() 函數中,您需要添加對 Move() 方法的調用。
function KeyDown(e)
{
switch(e.keyCode)
{
case 37: //Left
objects[player].Move("x", -speed);
break;
case 39: //Right
objects[player].Move("x", speed);
break;
case 38: //Up
objects[player].Move("y", -speed);
break;
case 40: //Down
objects[player].Move("y", speed);
break;
case 27: //Esc
if(timer==null)
{
Start();
}
else
{
Stop();
}
break;
}
}
現在您可以檢查渲染和控制。
碰撞時什么都沒有發生,但這將在以后修復。 首先,您需要確保刪除視圖中丟失的對象。 這是為了避免堵塞 RAM。
在 Car 類中,我們添加值為 false 的字段 dead,然后在 Update() 方法中對其進行更改:
if(this.y > canvas.height + 50)
{
this.dead=true;
}
現在您需要更改游戲的更新功能,替換與對象關聯的代碼:
var hasDead=false;
for(var i=0; i < objects.length; i++)
{
if(i !=player)
{
objects[i].Update();
if(objects[i].dead)
{
hasDead=true;
}
}
}
if(hasDead)
{
objects.shift();
}
如果您不移除物體,當生成太多汽車時,游戲將開始降低計算機速度。
游戲物體碰撞
現在您可以開始實施碰撞。 為此,請為 Car 類編寫一個方法 Collide(),它將檢查汽車的坐標:
Collide(car)
{
var hit=false;
if(this.y < car.y + car.image.height * scale && this.y + this.image.height * scale > car.y) //If the objects are on the same line horizontally
{
if(this.x + this.image.width * scale > car.x && this.x < car.x + car.image.width * scale) //If the objects are on the same line vertically
{
hit=true;
}
}
return hit;
}
現在我們需要在 Update() 函數中添加碰撞檢查:
var hit=false;
for(var i=0; i < objects.length; i++)
{
if(i !=player)
{
hit=objects[player].Collide(objects[i]);
if(hit)
{
alert("You crashed!");
Stop();
break;
}
}
}
這是游戲中的內容
碰撞時可以添加任何邏輯:
? 打開動畫;
? 添加效果;;
? 刪除對象;
? 健康狀況的改變,等等。
所有這些都由開發人員自行決定。
結論
這是一個非常簡單的游戲,但足以了解 JS 如何處理圖形以及一般如何創建游戲。 您可以在 GitHub 存儲庫中找到圖像和完整的游戲代碼。
使用畫布非常適合處理圖形:它提供了很棒的功能并且不會過多地加載瀏覽器。 我們現在也有一個可用的 WebGL 庫(示例和用法),它可以為您提供大量的性能和 3D 工作(canvas 無法做到這一點)。
理解 WebGL 可能很困難——也許相反,許多人對嘗試 Unity 引擎更感興趣,它知道如何編譯項目以在瀏覽器中運行它們。
關注七爪網,獲取更多APP/小程序/網站源碼資源!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。