Return True - نحوه اسکرول نرم در صفحه ( Smooth Scrolling )

نحوه اسکرول نرم در صفحه ( Smooth Scrolling )

در این آموزش قصد داریم به شکل ساده و بدون نیاز به کتابخانه ای خاص، اسکرول صفحه وب خود را تغییر داده و افکت اسکرول نرم را به آن اضافه کنیم

 

تفکر کلی

قصد ما این است که یک در بر گیرنده کلی با position ثابت ( fixed ) و همچنین overflow:hidden و یک فرزند مستقیم که بشه حرکتش داد ( وقتی اسکرول می کنیم ). ارتفاع  تگ body رو به اندازه همین فرزند میکنیم تا اسکرول صفحه به نمایش در بیاد. وقتی صفحه اسکرول میشه، آیتم که در ابتدای این توضیح موقعیتش رو در صفحه ثابت کردیم، جای خودش باقی میمونه ولی ما موقعیت فرزندش رو تغییر میدیم. این تکنیک باعث بوجود آمدن یک اسکرول نرم در برای صفحه شما می شود.

ساختار HTML

ساختار کلی صفحه در این مثال به شکل زیر است:

<body class="loading">
	<main>
		<div data-scroll>
			<!-- ... --->
		</div>
	</main>
</body>

 تگ main در صفحه ثابت خواهد شد و تگی که دارای ویژگی  [data-scroll] می باشد، در صفحه translate خواهد شد.

JAVASCRIPT

حالا نیاز به کدهای جاوا اسکریپت داریم. اول از همه چند متد کمکی و متغیرهایی برای کار خودمون درست کنیم

const MathUtils = {
    // map number x from range [a, b] to [c, d]
    map: (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c,
    // linear interpolation
    lerp: (a, b, n) => (1 - n) * a + n * b
};

const body = document.body;

برای محاسبات بعدی به سایز پنجره بخصوص ارتفاع آن نیاز داریم.

let winsize;
const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight};
calcWinsize();

 همچنین نیاز داریم این مقادیر را در هنگام تغییر سایز صفحه از نو محاسبه کنیم 

window.addEventListener('resize', calcWinsize);

 و همچنین نیاز داریم میزان اسکرول انجام شده را دنبال کنیم

let docScroll;
const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop;
window.addEventListener('scroll', getPageYScroll);

 حالا که توابع کمکی مورد نیاز را داریم، بیایید بدنه اصلی کد را بنویسیم. ابتدا یک کلاس برای اسکرول نرم ایجاد می کنیم

class SmoothScroll {
    constructor() {
        this.DOM = {main: document.querySelector('main')};
        this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]');
        ...
    }
}

new SmoothScroll();

 تا اینجا ما یک عنصر اصلی که همان main هست داریم ( همان المان دربرگیرنده ای که باید استایل fixed داشته باشد ) و فرزندی که باید اسکرول شود ( فرزندی که قرار است با translate اسکرول کردن را با آن شبیه سازی کنیم ).

حالا میخواهیم translateY را در هنگام اسکرول آپدیت کنیم اما ممکن است سایر ویژگی ها مانند scale و یا rotation را نیز بخواهیم آپدیت کنیم. پس یک شیئ ( object ) ایجاد می کنیم تا این تنظیمات را نگه دارد. در حال حاضر، فقط translationY

constructor() {
    ...

    this.renderedStyles = {
        translationY: {
            previous: 0, 
            current: 0, 
            ease: 0.1,
            setValue: () => docScroll
        }
    };
}

در ادامه برای بدست آوردن یک اسکرول نرم، از میزان اسکرول قبل و میزان اسکرولی که الان درش هستیم استفاده میکنیم. متغیرهای "previous" و "current" برای این کار در نظر گرفته شده اند. مقدار translationY فعلی، مقداری بین این دو متغیر است که کم کم افزایش می یابد. مقدار ease سرعت این تغییر بین دو متغیر را کنترل می کند. فرمول زیر برای محاسبه مقدار translation فعلی استفاده می شود.

previous = MathUtils.lerp(previous, current, ease)

 تابع setValue مقدار فعلی را ست می کند که در این مورد، مقدار فعلی اسکرول می باشد. پس این تنظیمات را در صفحه در هنگام لود شدن صفحه انجام می دهیم تا موقعیت درست translationY بدست آید.

constructor() {
    ...

    this.update();
}

update() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue();
    }   
    this.layout();
}

layout() {
    this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`;
}

 هر دو متغیر را ( current و previous ) را در ابتدا یکسان در نظر گرفتیم. و فقط در هنگام اسکرول صفحه این مقادیر باید تغییر کنند تا اسکرول صفحه با انیمیشن همراه شود. برای این کار تابع layout صدا زده می شود تا transformation  را بر روی المان مورد نظر ما ست کند. دقت کنید مقدار آن در زمان اسکرول به سمت بالا منفی خواهد بود.

برای تغییرات لایه، به موارد زیر نیاز داریم:

  • تگ main را فیکس و مقدار اورفلو آن را مخفی ( overflow:hidden ) کنیم تا به بالای صفحه بچسبد و اسکرول نشود.
  • ارتفاع تگ body را به اندازه ارتفاع مقدار محتوایی که داریم خواهیم کرد تا اسکرول صفحه نمایش داده شود.
constructor() {
    ...

    this.setSize();
    this.style();
}

setSize() {
    body.style.height = this.DOM.scrollable.scrollHeight + 'px';
}

style() {
    this.DOM.main.style.position = 'fixed';
    this.DOM.main.style.width = this.DOM.main.style.height = '100%';
    this.DOM.main.style.top = this.DOM.main.style.left = 0;
    this.DOM.main.style.overflow = 'hidden';
}

 همچنین نیاز داریم ارتفاع body را در هنگامی که سایز صفحه تغییر می کند از نو ست کنیم

constructor() {
    ...

    this.initEvents();
}

initEvents() {
    window.addEventListener('resize', () => this.setSize());
}

 حالا تابع خود را می زنیم تا مقادیر را بر طبق اسکرول انجام شده آپدیت کند

constructor() {
    ...

    requestAnimationFrame(() => this.render());
}

render() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].setValue();
        this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease);
    }
    this.layout();
    
    // for every item
    for (const item of this.items) {
        // if the item is inside the viewport call it's render function
        // this will update the item's inner image translation, based on the document scroll value and the item's position on the viewport
        if ( item.isVisible ) {
            item.render();
        }
    }
    
    // loop..
    requestAnimationFrame(() => this.render());
}

 در ادامه کلاس loading را از تگ body حذف و موقعیت scroll را می گیریم ( اگر قبل از آخرین رفرش صفحه را اسکرول کرده باشیم، این مقادیر ممکن است صفر نباشد ) و SmoothScroll را مقدار دهی اولیه می کنیم.

document.body.classList.remove('loading');
    // Get the scroll position
    getPageYScroll();
    // Initialize the Smooth Scrolling
    new SmoothScroll(document.querySelector('main'));

و حالا اسکرول نرم ایجاد شده.

 

DEMO

نظرات