Return True - انیمیشن تصاویر در صفحه هنگام اسکرول

انیمیشن تصاویر در صفحه هنگام اسکرول

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

تفکر کلی

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

ساختار HTML

یک آیتم در برگیرنده برای آیتمی که می خواهد پس زمینه بگیرد نیاز داریم  و overflow:hidden  خواهیم کرد.

<div class="item">
	<div class="item__img-wrap"><div class="item__img"></div></div>
	<!-- ... --->
</div>

CSS

در اینجا ما از padding به جای height برای در برگیرنده استفاده می کنیم در نتیجه می توانیم نسبت ظاهری درستی برای آیتم داخلی که تصویر پس زمینه دارد انتخاب کنیم. برای این کار از یک متغیر استفاده خواهیم کرد. پس تنها کار که مورد نیاز ست کردن عرض و ارتفاع تصویر است و ادامه کار را به CSS وا گذار میکنیم. برای خواندن بیشتر در باره این تکنیک به  Apect Ratio Boxes  در CSS-Tricks مراجعه کنید.

.item__img-wrap {
	--aspect-ratio: 1/1.5;
	overflow: hidden;
	width: 500px;
	max-width: 100%;
	padding-bottom: calc(100% / (var(--aspect-ratio))); 
	will-change: transform;
}

.item:first-child .item__img-wrap {
	--aspect-ratio: 8/10;
    --image: url(https://www.returntrue.io/laravel-filemanager/photos/1/demo/1.jpeg);
}

.item:nth-child(2) .item__img-wrap {
	width: 1000px;
	--aspect-ratio: 120/76;
    --image: url(https://www.returntrue.io/laravel-filemanager/photos/1/demo/2.jpeg);
}

...

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

.item__img {
	--overflow: 40px;
	height: calc(100% + (2 * var(--overflow)));
	top: calc( -1 * var(--overflow));
	width: 100%;
	position: absolute;
	background-image: var(--image);
	background-size: cover;
	background-position: 50% 0%;
	will-change: transform;
}

.item__img--t1 {
	--overflow: 60px;
}

.item__img--t2 {
	--overflow: 80px;
}

.item__img--t3 {
	

 JAVASCRIPT

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

در متد constructor از کلاس SmoothScroll تمامی کلاس های item را در صفحه پیدا کرده و در متغیر ذخیره می کنیم.

class SmoothScroll {
    constructor() {
        ...
        this.items = [];
        [...this.DOM.main.querySelectorAll('.content > .item')].forEach(item => this.items.push(new Item(item)));
        
        ...
    }
}

در متد render از کلاس  SmoothScroll  کد زیر را قرار می دهیم. این قسمت از کد، برای تمامی آیتم هایی که در اسکرول قابل مشاهده هستند عمل می کند و translation  آیتم هایی با تصویر را تنظیم می کند.

render() {
...

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();
        }
    }
...
}

از آنجایی که انجام این کار با تکیه بر ارتفاع تصویر است، ما نیاز داریم قبل از نمایش صفحه از لود شدن تصاویر برای بدست آوردن ارتفاع آنها اطمینان حاصل کنیم. به همین منظور از imagesLoaded استفاده خواهیم کرد

const preloadImages = () => {
    return new Promise((resolve, reject) => {
        imagesLoaded(document.querySelectorAll('.item__img'), {background: true}, resolve);
    });
};

 در ادامه پس از لود شدن تصاویر:

preloadImages().then(() => {
    document.body.classList.remove('loading');
    // Get the scroll position
    getPageYScroll();
    // Initialize the Smooth Scrolling
    new SmoothScroll(document.querySelector('main'));
});

 تا اینجا متدهای کلاس  اضافه کردن اسکرول نرم به صفحه را بر طبق نیازمون تغییر دادیم. حالا کلاس item برای هر یک از آیتم های صفحه ایجاد می کنیم.

class Item {
    constructor(el) {
        this.DOM = {el: el};
        this.DOM.image = this.DOM.el.querySelector('.item__img');
        
        this.renderedStyles = {
            innerTranslationY: {
                previous: 0, 
                current: 0, 
                ease: 0.1,
                maxValue: parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10),
                setValue: () => {
                    const maxValue = this.renderedStyles.innerTranslationY.maxValue;
                    const minValue = -1 * maxValue;
                    return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, minValue, maxValue), maxValue), minValue)
                }
            }
        };
    }
    ...
}

 منطق کلی این کلاس همانند کلاس SmoothScroll است. یک شیئ با نام renderedStyles به منظور نکهداری ویژگی هایی که می خواهیم آپدیت کنیم ساخته ایم. در این مورد ما آیتم دارای تصویر را حرکت می دهیم ( this.DOM.image ). تنها تفاوت در اینجا قرار دادن متغیری برای نگه داری حداکثر میزان حرکت است ( maxValue ) . این مقدار قبلا در متغیر CSS ست شده ( –overflow ). بنابراین کمترین مقدار translation را به شکل -1*maxVal محاسبه می کنیم.

تابع setValue کارهای زیر را انجام می دهد:

  • زمانی که مقدار بالای آیتم ( که به viewport وابسته است ) برابر ارتفاع پنجره نمایش ( یعنی آیتم در صفحه به نمایش در می آید ) می شود، مقدار translation به میزان کمترین مقدار در نظر گرفته شده ست می شود.
  • زمانی که مقدار بالای آیتم ( که به viewport وابسته است ) برابر منفی ارتفاع خود آیتم می شود ( یعنی آیتم دیگر در صفحه نمایش دیده نمی شود )  مقدار translation بهمیزان بیشترین مقدار در نظر گرفته شده ست می شود.

 در واقع ما بالای آیتم ( که به viewport وابسته است ) را رصد می کنیم و رنج عملیاتی آیتم به شکل زیر است:

[window’s height, -item’s height] to [minVal, maxVal]

 در ادامه نیاز داریم ارتفاع آیتم و موقعیت بالای آن را حساب کنیم.

constructor(el) {
    ...
    
    this.update();
}

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

layout() {
    this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`;
}

getSize() {
    const rect = this.DOM.el.getBoundingClientRect();
    this.props = {
        height: rect.height,
        top: docScroll + rect.top 
    }
}

 همین موارد را وقتی صفحه تغییر اندازه می دهد نیاز خواهیم داشت

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

در ادامه با استفاده از IntersectionObserver API  متغیر های مربوطه به مواردی که در صفحه در حال نمایش هستند را تغییر می دهیم

constructor(el) {
    ...

    this.observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0);
    });
    this.observer.observe(this.DOM.el);

    ...
}

کار تمام است. می توانید دمو را از لینک زیر ببینید

DEMO 

نظرات