プラグインやフレームワークの干渉を受けずに、生の JavaScript コードを UI に使用するだけのページをコーディングしています。
そして今、jQuery を使わずにページをスムーズにスクロールする方法を見つけるのに苦労しています。
プラグインやフレームワークの干渉を受けずに、生の JavaScript コードを UI に使用するだけのページをコーディングしています。
そして今、jQuery を使わずにページをスムーズにスクロールする方法を見つけるのに苦労しています。
JavaScript でのネイティブ ブラウザのスムーズ スクロールは次のようになります。
// scroll to specific values,
// same as window.scroll() method.
// for scrolling a particular distance, use window.scrollBy().
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// scroll certain amounts from current position
window.scrollBy({
top: 100, // negative value acceptable
left: 0,
behavior: 'smooth'
});
// scroll to a certain element
document.querySelector('.hello').scrollIntoView({
behavior: 'smooth'
});
このスムーズスクロールのデモ、または次のようなアルゴリズムを試してください。
self.pageYOffset
element.offsetTop
window.scrollTo
この質問に対する他の一般的な回答も参照してください。
Andrew Johnson の元のコード:
function currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
function elmYPosition(eID) {
var elm = document.getElementById(eID);
var y = elm.offsetTop;
var node = elm;
while (node.offsetParent && node.offsetParent != document.body) {
node = node.offsetParent;
y += node.offsetTop;
} return y;
}
function smoothScroll(eID) {
var startY = currentYPosition();
var stopY = elmYPosition(eID);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
scrollTo(0, stopY); return;
}
var speed = Math.round(distance / 100);
if (speed >= 20) speed = 20;
var step = Math.round(distance / 25);
var leapY = stopY > startY ? startY + step : startY - step;
var timer = 0;
if (stopY > startY) {
for ( var i=startY; i<stopY; i+=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
} return;
}
for ( var i=startY; i>stopY; i-=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
}
関連リンク:
要素をスクロールするには、scrollTop
時間の経過とともにその値を変更する必要があります。特定の時点について、新しいscrollTop
値を計算します。滑らかにアニメートするには、滑らかなステップ アルゴリズムを使用して補間します。
次のように計算scrollTop
します。
var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));
どこ:
start_time
アニメーションが開始された時間です。end_time
アニメーションが終了するとき(start_time + duration)
です。start_top
はscrollTop
先頭の値です。とdistance
は、目的の終了値と開始値の差です(target - start_top)
。堅牢なソリューションは、アニメーションの中断などを検出する必要があります。詳細については、jQuery を使用しないスムーズ スクロールに関する私の投稿をお読みください。
JSFiddleを参照してください。
コード:
/**
Smoothly scroll element to the given target (element.scrollTop)
for the given duration
Returns a promise that's fulfilled when done, or rejected if
interrupted
*/
var smooth_scroll_to = function(element, target, duration) {
target = Math.round(target);
duration = Math.round(duration);
if (duration < 0) {
return Promise.reject("bad duration");
}
if (duration === 0) {
element.scrollTop = target;
return Promise.resolve();
}
var start_time = Date.now();
var end_time = start_time + duration;
var start_top = element.scrollTop;
var distance = target - start_top;
// based on http://en.wikipedia.org/wiki/Smoothstep
var smooth_step = function(start, end, point) {
if(point <= start) { return 0; }
if(point >= end) { return 1; }
var x = (point - start) / (end - start); // interpolation
return x*x*(3 - 2*x);
}
return new Promise(function(resolve, reject) {
// This is to keep track of where the element's scrollTop is
// supposed to be, based on what we're doing
var previous_top = element.scrollTop;
// This is like a think function from a game loop
var scroll_frame = function() {
if(element.scrollTop != previous_top) {
reject("interrupted");
return;
}
// set the scrollTop for this frame
var now = Date.now();
var point = smooth_step(start_time, end_time, now);
var frameTop = Math.round(start_top + (distance * point));
element.scrollTop = frameTop;
// check if we're done!
if(now >= end_time) {
resolve();
return;
}
// If we were supposed to scroll but didn't, then we
// probably hit the limit, so consider it done; not
// interrupted.
if(element.scrollTop === previous_top
&& element.scrollTop !== frameTop) {
resolve();
return;
}
previous_top = element.scrollTop;
// schedule next frame for execution
setTimeout(scroll_frame, 0);
}
// boostrap the animation process
setTimeout(scroll_frame, 0);
});
}
ここで jQuery を使用しない例を作成しました: http://codepen.io/sorinnn/pen/ovzdq
/**
by Nemes Ioan Sorin - not an jQuery big fan
therefore this script is for those who love the old clean coding style
@id = the id of the element who need to bring into view
Note : this demo scrolls about 12.700 pixels from Link1 to Link3
*/
(function()
{
window.setTimeout = window.setTimeout; //
})();
var smoothScr = {
iterr : 30, // set timeout miliseconds ..decreased with 1ms for each iteration
tm : null, //timeout local variable
stopShow: function()
{
clearTimeout(this.tm); // stopp the timeout
this.iterr = 30; // reset milisec iterator to original value
},
getRealTop : function (el) // helper function instead of jQuery
{
var elm = el;
var realTop = 0;
do
{
realTop += elm.offsetTop;
elm = elm.offsetParent;
}
while(elm);
return realTop;
},
getPageScroll : function() // helper function instead of jQuery
{
var pgYoff = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
return pgYoff;
},
anim : function (id) // the main func
{
this.stopShow(); // for click on another button or link
var eOff, pOff, tOff, scrVal, pos, dir, step;
eOff = document.getElementById(id).offsetTop; // element offsetTop
tOff = this.getRealTop(document.getElementById(id).parentNode); // terminus point
pOff = this.getPageScroll(); // page offsetTop
if (pOff === null || isNaN(pOff) || pOff === 'undefined') pOff = 0;
scrVal = eOff - pOff; // actual scroll value;
if (scrVal > tOff)
{
pos = (eOff - tOff - pOff);
dir = 1;
}
if (scrVal < tOff)
{
pos = (pOff + tOff) - eOff;
dir = -1;
}
if(scrVal !== tOff)
{
step = ~~((pos / 4) +1) * dir;
if(this.iterr > 1) this.iterr -= 1;
else this.itter = 0; // decrease the timeout timer value but not below 0
window.scrollBy(0, step);
this.tm = window.setTimeout(function()
{
smoothScr.anim(id);
}, this.iterr);
}
if(scrVal === tOff)
{
this.stopShow(); // reset function values
return;
}
}
}
私は最近、jQuery がオプションではない状況でこの問題を解決しようと試みたので、後世のためにここに私の解決策を記録しています。
var scroll = (function() {
var elementPosition = function(a) {
return function() {
return a.getBoundingClientRect().top;
};
};
var scrolling = function( elementID ) {
var el = document.getElementById( elementID ),
elPos = elementPosition( el ),
duration = 400,
increment = Math.round( Math.abs( elPos() )/40 ),
time = Math.round( duration/increment ),
prev = 0,
E;
function scroller() {
E = elPos();
if (E === prev) {
return;
} else {
prev = E;
}
increment = (E > -20 && E < 20) ? ((E > - 5 && E < 5) ? 1 : 5) : increment;
if (E > 1 || E < -1) {
if (E < 0) {
window.scrollBy( 0,-increment );
} else {
window.scrollBy( 0,increment );
}
setTimeout(scroller, time);
} else {
el.scrollTo( 0,0 );
}
}
scroller();
};
return {
To: scrolling
}
})();
/* usage */
scroll.To('elementID');
このscroll()
関数は、Revealing Module パターンを使用して、関数で使用される値を設定するscrolling()
を介して、ターゲット要素の ID をその関数に渡します。scroll.To('id')
scroller()
壊す
でscrolling()
:
el
: 対象の DOM オブジェクトelPos
elememtPosition()
:呼び出されるたびに、ページの上部に対するターゲット要素の相対位置を与える関数を返します。duration
: 遷移時間 (ミリ秒)。increment
: 対象要素の開始位置を 40 段階に分割します。time
: 各ステップのタイミングを設定します。prev
: でのターゲット要素の以前の位置scroller()
。E
: 内のターゲット要素の位置を保持しscroller()
ます。実際の作業は、対象の要素がページの一番上に来るか、ページがスクロールできなくなるまでscroller()
( を介して) 自分自身を呼び出し続ける関数によって行われます。setTimeout()
が呼び出されるたびscroller()
に、ターゲット要素 ( variable に保持されている) の現在の位置をチェックし、E
それが> 1
OR< -1
であり、ページがまだスクロール可能である場合は、 が正の値か負の値かincrement
に応じて、ウィンドウをピクセル単位で上下にシフトします。ORも===でもないE
場合、関数は停止します。ターゲット要素がウィンドウの上部にあることを確認するためだけに、完了時にメソッドを追加しました(ピクセルの何分の 1 かで外に出ていることに気付かないでください!)。E
> 1
< -1
E
prev
DOMElement.scrollTo()
のif
2 行目のステートメントはscroller()
、ページがスクロールしているかどうか (ターゲットがページの下部にあり、ページがそれ以上スクロールできない場合) をE
、前の位置 ( prev
) と照合して確認します。
その下の三項条件は、ゼロに近づくにつれてincrement
値を減らします。E
これにより、ページが一方向にオーバーシュートし、次に跳ね返ってもう一方をオーバーシュートし、次に跳ね返ってもう一方をオーバーシュートすることがなくなります。これは、ピンポン スタイルで、無限大とそれ以上になります。
ページの高さが c.4000px を超える場合、三項式の最初の条件の値 (ここでは +/-20) および/またはincrement
値を設定する除数 (ここでは 40) を増やしたい場合があります。
duration
、 を設定する除数、increment
および の三項条件の値scroller()
をいじってみると、ページに合わせて関数を調整できるはずです。
NBLubuntu の Firefox と Chrome の最新バージョン、および Windows8 の Firefox、Chrome、IE でテスト済みです。
私はこのようなものを作りました。IE8で動作するかどうかはわかりません。IE9、Mozilla、Chrome、Edge でテスト済み。
function scroll(toElement, speed) {
var windowObject = window;
var windowPos = windowObject.pageYOffset;
var pointer = toElement.getAttribute('href').slice(1);
var elem = document.getElementById(pointer);
var elemOffset = elem.offsetTop;
var counter = setInterval(function() {
windowPos;
if (windowPos > elemOffset) { // from bottom to top
windowObject.scrollTo(0, windowPos);
windowPos -= speed;
if (windowPos <= elemOffset) { // scrolling until elemOffset is higher than scrollbar position, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
} else { // from top to bottom
windowObject.scrollTo(0, windowPos);
windowPos += speed;
if (windowPos >= elemOffset) { // scroll until scrollbar is lower than element, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
}
}, 1);
}
//call example
var navPointer = document.getElementsByClassName('nav__anchor');
for (i = 0; i < navPointer.length; i++) {
navPointer[i].addEventListener('click', function(e) {
scroll(this, 18);
e.preventDefault();
});
}
説明
pointer
—要素を取得し、属性「href」があるかどうかを確認し、ある場合は「#」を取り除きますelem
—「#」のないポインター変数elemOffset
—ページの上部からの「スクロール先」要素のオフセット使用できます
document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});
ページの上部にスクロールしたい場合は、空の要素を上部に配置して、その要素までスムーズにスクロールできます。
ここに私のバリエーションがあります:
let MenuItem = function ( _menuItem ) {
// I had a sticky header, so its height had to be taken into account when scrolling
let _header = document.querySelector('.site-header');
let _scrollToBlock = function( e, menuItem ) {
let id = menuItem.getAttribute('href'), // the href attribute stores the id of the block to which the scroll will be
headerHeight = _header.offsetHeight; // determine the height of the header
id = id.replace(/#/, ''); // remove the # sign from the id block
let elem = document.getElementById( id ), // define the element to which we will scroll
top = elem.getBoundingClientRect().top + window.scrollY - headerHeight; // determine the height of the scroll
window.scroll({
top: top,
left: 0,
behavior: 'smooth'
});
},
_addEvents = function() {
_menuItem.addEventListener('click', function (e){
e.preventDefault(); // Disable redirect on click
_scrollToBlock(e, _menuItem);
});
},
_init = function() {
_addEvents();
};
_init();
};
// Initialize the class MenuItem to all links with class .menu__item
document.querySelectorAll('.menu__item').forEach( function(item) {
new MenuItem(item);
} );