自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用原生 JS 寫一個簡易版的臺球

開發(fā) 前端
requestAnimationFrame就是一個JS動畫幀,簡單來說和定時器有點相似,但是動畫呈現(xiàn)出來的效果比定時器更流暢,性能更好。

前言

突發(fā)奇想想用JS寫一個臺球小游戲,磕磕碰碰之后,算是實現(xiàn)了一個簡易版的。用到的知識主要是通過遞歸來調(diào)用requestAnimationFrame,以及一些簡單的三角函數(shù)角度計算。requestAnimationFrame就是一個JS動畫幀,簡單來說和定時器有點相似,但是動畫呈現(xiàn)出來的效果比定時器更流暢,性能更好。

1、繪制游戲元素

CSS

// CSS
.table {
position: relative;
margin: 100px auto;
width: 1080px;
height: 596px;
background: url(./臺球桌.jpg) no-repeat;
background-size: 100%;
}

.big {
position: absolute;
width: 1000px;
height: 500px;
left: 43px;
top: 48px;
}

.box,
.box2 {
width: 50px;
height: 50px;
border-radius: 50%;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
position: absolute;
}

.box {
background: radial-gradient(circle at 75% 30%, #fff 5px, #fffbfef1 8%, #aaaaaac4 60%, #faf6f9bd 100%);
}

.box2 {
background: radial-gradient(circle at 75% 30%, #fff 5px, #ff21f4f1 8%, #d61d1dc4 60%, #ff219b 100%);
}

.big .box::before,
.box2::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
border-radius: 50%;
}

.gan {
display: flex;
height: 20px;
position: absolute;
left: 25px;
top: 15px;
transform-origin: 0 50%;
transform: rotate(50deg);
cursor: pointer;
}

.gan2 {
width: 25px;
height: 20px;
}

.gan3 {
width: 375px;
height: 20px;
background: url(./Snipaste_2022-07-18_19-52-54.jpg) no-repeat center;
background-size: 100%;
}

html

//html
<div class="table">
<div class="big">
<div class="box">
<div class="gan">
<div class="gan2"></div>
<div class="gan3"></div>
</div>
</div>
<div class="box2"></div>
</div>
</div>

JS

//JS
// 設(shè)置球的位置
//母球
const box1 = document.querySelector('.box')
box1.style.left = '300px'
box1.style.top = '150px'
//子球
const box2 = document.querySelector('.box2')
box2.style.left = '700px'
box2.style.top = '300px'
//球桿
const gan = document.querySelector('.gan')
const gan2 = document.querySelector('.gan2')
const gan3 = document.querySelector('.gan3')

2、球桿跟隨鼠標(biāo)旋轉(zhuǎn)

先獲取鼠標(biāo)在頁面的坐標(biāo),然后減去球心的坐標(biāo),就得到了一個相對坐標(biāo)。然后把球心當(dāng)成原點,計算出鼠標(biāo)相對球心的角度,最后把這個角度賦值給球桿的transform屬性,就可以實現(xiàn)球桿跟隨鼠標(biāo)旋轉(zhuǎn)的效果了

//聲明鼠標(biāo)相對坐標(biāo)變量
let x, y
// 獲取鼠標(biāo)的坐標(biāo),來計算球桿的角度
document.addEventListener('mousemove', function (e) {
const position = box1.getBoundingClientRect()
// 獲取鼠標(biāo)相對球心的坐標(biāo),因為盒子的position原點在左上角,所以要減去自身寬高的一半才是球心
x = e.pageX - position.left - 25
y = e.pageY - position.top - 25 - document.documentElement.scrollTop
let z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); // 勾股定理計算斜邊值
let cos = y / z;// 余弦
let radian = Math.acos(cos);//用反三角函數(shù)求弧度
let angle = 180 / (Math.PI / radian);//將弧度轉(zhuǎn)換成角度
if (x > 0 && y > 0) {//鼠標(biāo)在第四象限
angle = 90 - angle
}
if (x == 0 && y > 0) {//鼠標(biāo)在y軸負(fù)方向上
angle = 90;
}
if (x == 0 && y < 0) {//鼠標(biāo)在y軸正方向上
angle = 270;
}
if (x > 0 && y == 0) {//鼠標(biāo)在x軸正方向上
angle = 0;
}
if (x < 0 && y > 0) {//鼠標(biāo)在第三象限
angle = 90 + angle
}
if (x < 0 && y == 0) {//鼠標(biāo)在x軸負(fù)方向
angle = 180;
}
if (x < 0 && y < 0) {//鼠標(biāo)在第二象限
angle = 90 + angle
}
if (x > 0 && y < 0) {//鼠標(biāo)在第一象限
angle = 450 - angle
}
// 把計算出來的角度取模后賦值給球桿旋轉(zhuǎn)角度
gan.style.transform = `rotate(${angle % 360}deg)`
})

3、球桿的擊球動畫

球桿其實是由 3 個盒子組成的,最外面的大盒子來控制球桿的旋轉(zhuǎn),大盒子里面有兩個盒子 gan2 和 gan3, gan3 這個盒子用來放球桿的圖片。gan2 這個盒子是看不到的,它負(fù)責(zé)把球桿向外面撐開。所以球桿的動畫就很簡單了,只要增加和減少 gan2 盒子的寬,就能實現(xiàn)球桿的伸縮了。

實現(xiàn)動畫就是用尾遞歸來重復(fù)調(diào)用 requestAnimationFrame 函數(shù)。

// // 球桿點擊事件
document.querySelector('.gan3').addEventListener('click', function () {
moveGan(gan2, 0)
})
// 球桿打擊動畫
function moveGan(item, num) {
// i來控制函數(shù)的結(jié)束條件
let i = num
requestAnimationFrame(() {
//獲取元素的坐標(biāo)值,要把字符串里的數(shù)字提取出來
let moveX = parseFloat(item.style.width) || 25
moveX += 15
// 每一次調(diào)用這個函數(shù),就讓元素的寬+15px
item.style.width = moveX + 'px'
i++
if (i >= 10) {
// i>10時,就讓球桿再縮回去
return returnGan(item, 0)
}
// 使用尾遞歸來重復(fù)調(diào)用
return moveGan(item, i)
})
}
function returnGan(item, num) {
let i = num
requestAnimationFrame(() {
let moveX = parseFloat(item.style.width) || 0
moveX -= 15
// 每一次調(diào)用這個函數(shù),就讓元素的寬-15px
item.style.width = moveX + 'px'
i++
if (i >= 10) {
return tick() //tick是擊球的函數(shù)
}
return returnGan(item, i)
})
}

4、球桿擊球后,母球的移動

母球的擊球動畫同樣是通過尾遞歸來重復(fù)調(diào)用 requestAnimationFrame 函數(shù),但是涉及到墻壁反彈,以及撞擊子秋,母球的移動函數(shù)的參數(shù)會復(fù)雜一點。

母球移動的速度和距離,是通過i這個變量來控制的,這個函數(shù)每調(diào)用一次,i 會遞減。x 和 y 這兩個參數(shù)會接收一個 -1 到 1 之間的值,起到一個方向系數(shù)的效果,通過參數(shù)把球桿的撞擊方向傳遞進(jìn)來。碰到邊界之后,就把對應(yīng)的系數(shù)取負(fù),然后用新系數(shù)執(zhí)行移動函數(shù),就能起到反彈的效果了。

// 擊打母球的函數(shù)
function tick() {
// 通過絕對值判斷打擊角度,xy就是鼠標(biāo)相對球心的坐標(biāo)
if (Math.abs(x) > Math.abs(y)) {
// 通過判斷x,y是否大于0,判斷打擊方向
if (x > 0 && y > 0 || x > 0 && y < 0) {
raf(box1, -1, -1 / (x / y), 1000)
} else {
raf(box1, 1, 1 / (x / y), 1000)
}
} else {
if (y > 0 && x > 0 || y > 0 && x < 0) {
raf(box1, -1 / (y / x), -1, 1000)
} else {
raf(box1, 1 / (y / x), 1, 1000)
}
}
}

//..... 母球移動的函數(shù)里面還要加代碼,所以這里就先不貼出來了。

// 判斷是否進(jìn)洞的函數(shù)
function test(x, y) {
if (x < 10 && y < 10 || x > 940 && y < 10 || x > 940 && y > 440 || x < 10 && y > 440
|| x > 475 && x < 525 && y < 5 || x > 475 && x < 525 && y > 445) {
return true
}
}

5、母球撞擊子球移動

這是最麻煩的一步,撞擊后兩個球的運動軌跡都會發(fā)生變化。只考慮最普通的撞擊,子球的運動方向應(yīng)該是撞擊點與子球球心這條直線的方向,這個比較好計算。母球的撞擊后的方向應(yīng)該是以撞擊點的那條切線進(jìn)行反彈,三角函數(shù)幾乎忘光了,這個我也不知道怎么計算了,所以用了個簡易的算法,就和撞墻壁一樣直接反彈,這樣會導(dǎo)致某些角度下,母球撞擊之后的方向不正常。

把這個撞擊判斷加到母球移動的函數(shù)里面,然后再補充一個子球的移動函數(shù),整個代碼就寫完了

//母球移動
// 獲取坐標(biāo),要把字符串里的數(shù)字提取出來
let fx = parseFloat(box1.style.left)
let fy = parseFloat(box1.style.top)
let gx = parseFloat(box2.style.left)
let gy = parseFloat(box2.style.top)
// 聲明用判斷撞球角度的變量
let n
// 控制子球移動函數(shù)的調(diào)用
let p = true
function raf(item, x, y, num) {
//擊球后隱藏球桿
gan3.style.display = 'none'
// item是目標(biāo)元素,xy對應(yīng)移動方向的系數(shù),i用來控制移動速度
let i = num
requestAnimationFrame(() {
fx += x * 5 * i / 500
fy += y * 5 * i / 500
item.style.left = fx + 'px'
item.style.top = fy + 'px'
i -= 2
// 邊界判斷,球桌寬1000500,球?qū)捀?span style="color: #005cc5;">50,所以邊界就是0-950
if (fx > 950) { // 右邊界,讓x系數(shù)反過來
fx = 950
return raf(item, -x, y, i)
} else if (fy > 450) { // 下邊界,讓y系數(shù)反過來
fy = 450
return raf(item, x, -y, i)
} else if (fx < 0) { // 左邊界,讓x系數(shù)反過來
fx = 0
return raf(item, -x, y, i)
} else if (fy < 0) { // 上邊界,讓y系數(shù)反過來
fy = 0
return raf(item, x, -y, i)
}
// i<=50就停止移動,然后顯示球桿
if (i <= 50) return gan3.style.display = 'block'
// 判斷球是否進(jìn)洞
if (test(fx, fy)) {
return item.style.display = 'none'
}
//兩個球撞擊時的判斷
if (fx < gx + 50 && fx > gx - 50 && fy < gy + 50 && fy > gy - 50) {
// 子球前進(jìn)的角度,就是撞擊時,兩個圓心連線的夾角
n = Math.abs(gx - fx) >= Math.abs(gy - fy) ? Math.abs(gx - fx) : Math.abs(gy - fy)
// n用來控制調(diào)用函數(shù)時x,y的大小,不能大于1,否則移動速度會異常
if (p) raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
// 只有第一次碰撞時,會調(diào)用一次子球移動的函數(shù),避免一次擊球產(chǎn)生多次撞擊時,這個函數(shù)被多次調(diào)用
p = false
return raf(item, -x, y, i)
}
return raf(item, x, y, i)
})
}
//子球移動
function raf2(item, x, y, num) {
let i = num
requestAnimationFrame(() {
//獲取元素的坐標(biāo)值,要把字符串里的數(shù)字提取出來
gx += x * 5 * i / 700
gy += y * 5 * i / 700
item.style.left = gx + 'px'
item.style.top = gy + 'px'
i -= 2
if (gx > 950) {
gx = 950
return raf2(item, -x, y, i)
} else if (gy > 450) {
gy = 450
return raf2(item, x, -y, i)
} else if (gx < 0) {
gx = 0
return raf2(item, -x, y, i)
} else if (gy < 0) {
gy = 0
return raf2(item, x, -y, i)
}
//兩個球觸碰判斷
if (fx < gx + 50 && fx > gx - 50 && fy < gy + 50 && fy > gy - 50) {
return raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
}
if (i <= 50) return p = true // 移動函數(shù)執(zhí)行完后,重置p這個變量
// 判斷球是否進(jìn)洞
if (test(gx, gy)) {
return item.style.display = 'none'
}
return raf2(item, x, y, i)
})
}

圖片

總結(jié)

圖片

這個小游戲?qū)崿F(xiàn)的并不完美,因為用到了太多的遞歸,很多細(xì)節(jié)方面不好控制,球的運動軌跡也很難計算,在某些角度下會出現(xiàn)BUG。球雖然是圓的,但是它的盒子是正方形,所以撞擊有的時候會看著很奇怪。移動的函數(shù)寫的也有缺陷,它不能復(fù)用,如果想添加多個球,函數(shù)就得改。

這個破產(chǎn)版的臺球主要就是寫著玩一玩,嘗試了一下JS動畫的實現(xiàn) , 不喜勿噴。

責(zé)任編輯:姜華 來源: 前端YUE
相關(guān)推薦

2024-02-06 10:04:49

Express框架repo

2020-09-29 09:41:50

Spring Boot項目代碼

2023-12-29 08:31:49

Spring框架模塊

2017-01-13 08:37:57

PythonAlphaGoMuGo

2021-04-23 16:40:49

Three.js前端代碼

2013-06-18 09:51:52

PomeloPomelo平臺搭建平臺

2021-07-12 15:50:55

Go 語言netstat命令

2018-12-04 13:30:28

Javascript編譯原理前端

2022-02-11 13:44:56

fiber架構(gòu)React

2020-10-29 16:00:03

Node.jsweb前端

2023-04-07 15:45:13

Emojicode開源編碼語言

2023-04-10 14:20:47

ChatGPTRESTAPI

2017-06-08 15:53:38

PythonWeb框架

2011-12-05 10:37:53

Linux服務(wù)器Shell腳本

2022-03-24 14:42:19

Python編程語言

2018-10-31 10:11:24

Python編程語言語音播放

2023-09-06 09:54:12

AI模型

2022-10-08 00:06:00

JS運行V8

2022-04-06 18:29:58

CSSJS輸入框

2021-05-06 15:05:57

Python自動化工具
點贊
收藏

51CTO技術(shù)棧公眾號