Плавный scroll с помощью window.scrollBy()
Всем привет. Подскажите пожалуйста, как сделать плавный scroll c помощью window.scrollBy().
const btnPort = document.getElementById('port');
btnPort.addEventListener('click', (e) => {
e.preventDefault();
window.scrollBy(0, 800);
});
.final {
margin-top: 800px;
}
<div class="promo">
<div class="promo__btns">
<a class="promo__link btn" href="#" id="port">НАЖМИ</a>
</div>
</div>
<div class="final">
<h1 class="final__block">ФИНАЛ<h1>
<div>
Ответы (2 шт):
Автор решения: Евгений Николаев
→ Ссылка
В scrollBy можно передать options объект, в котором параметр behavior отвечает за поведение прокрутки.
const btnPort = document.getElementById('port');
btnPort.addEventListener('click', (e) => {
e.preventDefault();
window.scrollBy({
top: 800,
behavior : "smooth"
});
});
.final {
margin-top: 800px;
}
<div class="promo">
<div class="promo__btns">
<a class="promo__link btn" href="#" id="port">НАЖМИ</a>
</div>
</div>
<div class="final">
<h1 class="final__block">ФИНАЛ<h1>
<div>
Автор решения: ᅠhᅠ
→ Ссылка
Поддержка сафари + некоторые настройки.
class OptionsParser {
constructor(str = '', defaultOptions) {
this.defaultOptions = defaultOptions
this.options = {}
str && this.parseOptions(str)
this.applyOptions()
}
parseOptions(str) {
str
.split(',')
.map(option => option.trim())
.filter(option => !!option)
.forEach(option => {
const parts = option
.split('=')
.map(option => option.trim())
.filter(option => !!option)
if(parts[1] === 'false') {
this.options[parts[0]] = false
}
else if(parts[1] === 'true') {
this.options[parts[0]] = true
}
else if(isNaN(parts[1])) {
this.options[parts[0]] = parts[1] ? parts[1] : true
}
else if(!isNaN(parts[1])){
this.options[parts[0]] = +parts[1]
}
})
}
applyOptions() {
for(const member in this.defaultOptions) {
this.options[member] = this.options[member] || this.defaultOptions[member]
}
}
}
utils = {}
utils.cumulativeTop = function(element) {
let top = 0
do {
top += element.offsetTop || 0
element = element.offsetParent
} while (element)
return top
}
utils.easings = {
linear: t => t,
easeInQuad: t => t*t,
easeOutQuad: t => t*(2-t),
easeInOutQuad: t => t<.5 ? 2*t*t : -1+(4-2*t)*t,
easeInCubic: t => t*t*t,
easeOutCubic: t => (--t)*t*t+1,
easeInOutCubic: t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1,
easeInQuart: t => t*t*t*t,
easeOutQuart: t => 1-(--t)*t*t*t,
easeInOutQuart: t => t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t,
easeInQuint: t => t*t*t*t*t,
easeOutQuint: t => 1+(--t)*t*t*t*t,
easeInOutQuint: t => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t,
}
class ScrollTo extends OptionsParser{
constructor(trigger) {
super(
trigger.getAttribute('data-options'),
{
align: 0,
durationStretch: 1,
ease: 'easeInOutCubic',
offsetFrom: null
}
)
this.trigger = trigger
if(!this.trigger) return;
this.targetName = this.trigger.getAttribute('data-scroll-to')
this.target = document.querySelector(this.targetName) || document.body
this.options.ease = utils.easings[this.options.ease] || utils.easings.easeOutCubic
this.options.offsetFrom = document.querySelector(this.options.offsetFrom) || null
this.start = this.start.bind(this)
this.move = this.move.bind(this)
this.stop = this.stop.bind(this)
this.frameId = null
this.currentScroll = 0
this.to = 0
this.align = 0
this.offsetFrom = 0
this.startTime = 0
this.listen()
}
listen() {
this.trigger.addEventListener('click', this.start)
}
start() {
document.addEventListener('wheel', this.stop)
document.addEventListener('touchstart', this.stop)
setTimeout(() => {
this.offsetFrom = this.options.offsetFrom ? -this.options.offsetFrom.offsetHeight : 0
this.align = this.options.align ? this.options.align * innerHeight - this.target.offsetHeight / 2 : 0
this.currentScroll = scrollY
this.to = this.currentScroll - (utils.cumulativeTop(this.target) + this.offsetFrom) + this.align
this.startTime = Date.now()
this.frameId = requestAnimationFrame(this.move)
}, 0)
}
move() {
this.frameId = requestAnimationFrame(this.move)
let t = (Date.now() - this.startTime) / (1000 * this.options.durationStretch)
t = this.options.ease(t)
window.scrollTo(0, Math.abs(this.currentScroll - t * this.to))
if(t >= 1) this.stop()
}
stop() {
document.removeEventListener('wheel', this.stop)
document.removeEventListener('touchstart', this.stop)
cancelAnimationFrame(this.frameId)
}
}
document.querySelectorAll('[data-scroll-to]').forEach(trigger => new ScrollTo(trigger))
body {
margin: 0;
height: 1000vh;
}
.header-placeholder {
height: 10vh;
}
header {
position: fixed;
height: 10vh;
width: 100%;
top: 0;
left: 0;
background-color: lightpink;
}
.tomato {
background: tomato;
}
.lightblue {
background: lightblue;
}
.lightgreen {
background: lightgreen;
}
section {
height: 50vh;
margin-top: 100vh;
}
.buttons {
position: fixed;
top: 10vh;
}
<div class="header-placeholder"></div>
<header></header>
<div class="buttons">
<button data-scroll-to=".section-1" data-options="offsetFrom = .header-placeholder" class="tomato">click</button>
<button data-scroll-to=".section-2" data-options="durationStretch = 2, align = 0.5, ease = easeOutQuint" class="lightblue">click</button>
<button data-scroll-to=".section-3" data-options="align = 0.75" class="lightgreen">click</button>
</div>
<section class="section-1 tomato"></section>
<section class="section-2 lightblue"></section>
<section class="section-3 lightgreen"></section>