之前的我的網站是用 VuePress 的 beta 版搭建的,當時還挺新鮮,放到如今感受已經爛大街了,之後面試也撈不到好處,決定從新寫一個。
想在本身的網站上實現一個劫持鼠標滾輪,進行翻頁的效果,記錄一下踩坑之路。
先來看看模仿目標 明日方舟官網 ,記得小米和一加的手機介紹頁也用過這種滾動css
最終效果✌️
html
要實現 banner 圖片不跟隨內容滾動,只須要一個很是簡單的 css 屬性 background-attachment
就能實現。
固然也能夠直接在 img 標籤上經過 posistion
屬性來固定,可是須要設置其餘如 margin
、z-index
各類屬性,仍是不太方便。ios
<!-- demo -->
<div class="banner"></div>
<div style="height: 100vh;"></div>
<style lang="scss"> .banner { height: 80vh; background-image: linear-gradient(#000000, #ffffff); /* 保持背景圖片不跟隨內容滾動 */ background-attachment: fixed; } </style>
複製代碼
固定完背景,接下來就是攔截默認的鼠標滾輪事件,實現本身的翻頁效果,原本是很是簡單的 addEventListener
git
window.addEventListener('mousewheel', scrollFn)
function scrollFn (e) {
e.preventDefault()
if (e.deltaY > 0) {
// 向下滾動...
} else {
// 向上滾動...
}
}
複製代碼
可是在瀏覽器上報了這樣的錯誤,並且默認的鼠標滾輪事件仍然能觸發。。web
點進去翻了一下 chrome 的 feature ,發現 Event 多出來了一個 passive
屬性,並且 WheelEvent 的 passive
默認爲 true,
又翻了翻 addEventListener
的 MDN ,發現面試
多了一個 options 的選項。。
由於addEventListener
的useCapture
屬性用的人太少,15年末已經被規範爲可選屬性,而且可以傳入對象。passive
的做用就是讓 listener 禁止調用preventDefault()
修改成 window.addEventListener('mousewheel', this.scrollFn, { passive: false })
最終成功實現滾輪默認事件chrome
使用 css 屬性 scroll-behavior: smooth
是最方便最簡單的,效果也比第二次要好不少,可是 mac 上 safari 不支持這一特性,ios 上的 safari 卻又支持😓npm
function scrollFn (e: WheelEvent) {
e.preventDefault()
// 獲取視窗高度
const windowHeight = window.innerHeight || document.body.clientHeight
// 計算 banner 高度,css 屬性爲80vh
const scrollHeight = windowHeight * 0.8
window.scrollTo(0, e.deltaY > 0 ? scrollHeight : 0)
}
複製代碼
在搜索 safari 上的相似屬性時,發現了更方便的方法 window.scrollTo({ top: 1000, behavior: "smooth" })
,嘗試了一下,mac 的 safari 也不支持。。canvas
爲了可以兼容 safari,這個翻頁效果仍是得本身手寫。經過使用 requestAnimationFrame
方法來進行滾動動畫操做,在瀏覽器重繪以前調用動畫函數。通常根據顯示器的幀數來進行調用,並且可以在頁面 blur 時中止動畫,節省資源。api
function scrollFn (e: WheelEvent) {
e.preventDefault()
let start = 0
// 動畫函數,須要閉包訪問 start
const step = (unix: number) => {
if (!start) {
start = unix
}
const duration = unix - start
// 獲取視窗高度
const windowHeight = window.innerHeight || document.body.clientHeight
// 計算 banner 高度,css 屬性爲80vh
const scrollHeight = windowHeight * 0.8
// 在當前時間應該滾動的距離
const nowY = scrollHeight / 1000 * duration
window.scrollTo(0, e.deltaY > 0 ? nowY : scrollHeight - nowY)
// 1000ms
if (duration < 1000) {
requestAnimationFrame(step)
}
}
requestAnimationFrame(step)
}
複製代碼
敲完代碼一看,效果有點拉胯
這直上直下的線性效果,太呆了
在搜索引擎裏一頓查,可大多都是數學題和 canvas,還翻了好多關於貝塞爾曲線的解析,難度仍是有點高的嗷(方向錯了😅)
在我覺得只能放棄本身手寫,藉助 npm 的力量時,最後在一堆數學題裏翻出來 這個網站,救我🐶命
現實生活中,物體並非忽然啓動或者中止,固然也不可能一直保持勻速移動。就像咱們打開抽屜的過程那樣,剛開始拉的那一下動做很快,可是當抽屜被拉出來以後咱們會不自覺的放慢動做。掉落在地板上的東西,一開始降低的速度很快,後來就會在地板上來回反彈直到中止。
經過使用網站裏的緩動函數,可以在必定程度上模擬真實環境中的物體運動效果,來給動畫添加真是的效果
function scrollFn (e: WheelEvent) {
e.preventDefault()
// 正在滾動中,或者到最後一頁還向下滾,或者第一頁還向上滾
if (this.debounce || (e.deltaY > 0 && this.index >= 1) || (e.deltaY < 0 && this.index === 0)) {
return
}
let start = 0
// 動畫函數,須要閉包訪問 start 就沒有分離出來
const step = (unix: number) => {
if (!start) {
start = unix
}
const duration = unix - start
// 獲取視窗高度
const windowHeight = window.innerHeight || document.body.clientHeight
// 計算 banner 高度,css 屬性爲80vh
const scrollHeight = windowHeight * 0.8
// 1000ms內,duration / 1000就會在0-1之間增長,返回值也是,再乘上最終的高度
const y = this.easeInOutCubic(duration / 1000) * scrollHeight
window.scrollTo(0, e.deltaY > 0 ? y : scrollHeight - y)
if (duration <= 1001) {
requestAnimationFrame(step)
this.debounce = true
} else {
this.debounce = false
e.deltaY > 0 ? this.index++ : this.index--
console.log(this.index)
}
}
requestAnimationFrame(step)
}
// 緩動函數,x的範圍爲0-1,返回的 number 也是0-1 https://easings.net#easeInOutCubic
function easeInOutCubic (x: number): number {
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
複製代碼
緩動函數 easeInOutCubic
會根據傳入的 x 的範圍 0-1 輸出相應的快慢的從 0% 到 100%,就像 easings.net#easeInOutCubic 裏的例子同樣,對 requestAnimationFrame
的速度進行控制
從👎到👍
requestAnimationFrame
scroll-behavior: smooth
requestAnimationFrame with easings
scroll-behavior: smooth
最方便,自帶緩動函數,可是 safari 不支持,完成動畫的時間也不可控