barbajs / barba

Create badass, fluid and smooth transitions between your website’s pages
https://barba.js.org/
MIT License
11.65k stars 473 forks source link

Why is it so hard to understand how it works? #357

Closed bdadev closed 5 years ago

bdadev commented 5 years ago

For some time I've been trying to use it on my web pages but I've never been successful. I create animations functions with GSAP plus I have great difficulty using them with namespace. Version 2.0 seemed to me to be clearer but still my ignorance would not allow me to use it. I've seen a lot of tutorials on the internet with none of the simplicity that uqero use. Can any pure soul help me? Here is a simple implementation that did not work ...

I'm sorry for my fluent English. :(

import {
    TimelineMax,
    CSSPlugin,
    AttrPlugin,
    TweenMax,
} from 'gsap/all';
import barba from '@barba/core';
import router from '@barba/router';

const plugins = [CSSPlugin, AttrPlugin];

const titleHome = document.querySelector('[data-title="home"]');
const titleAbout = document.querySelector('[data-title="about"]');
const titleBlog = document.querySelector('[data-title="blog"]');
const titleContact = document.querySelector('[data-title="contact"]');

function titleLeave(e) {
    TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1 });
}

function titleEnter(e) {
    TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1 });
}

barba.init({
    transitions: [{
        sync: true,
        leave: (data) => {
            if (data.current.namespace === 'home') {
                TweenMax.fromTo(titleHome, 2, { opacity: 1 }, { opacity: 0 });
            }
        },
        enter: ({ next }) => {
            if (next.namespace === 'about') {
                TweenMax.fromTo(titleAbout, 2, { opacity: 0 }, { opacity: 1 });
            }
        },
    }],
});
thierrymichel commented 5 years ago

Hi @bdadev,

Thanks for using barba.js (or trying to ;P). It will help us improving documentation and/or creating more meaningful examples.

Transitions are ASYNCHRONOUS. So, leave and enter hooks sould be ASYNCHRONOUS. But your code, even if it uses some TweenMax, is SYNCHRONOUS.

Why? Let's try to explain… For the purpose, I will change you scenario and remove the sync: true (because it is easier to understand, but the same logic applies…). We want a leave transition THEN an enter transition.

When you execute TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1 });, your code do not know when your tween will end. It is just a "one-line-synchronous" execution. Of course, there will be a tween on the element but you have no way to know when it is finished to start the next tween.

There are different ways to manage it. We could have chosen to add some duration parameter (leave(data, duration) {}). But that would have involved that you change it every time you modify the duration of your tween.

I hope you follow me.

Rather than that, barba.js expect that your code is asynchronous. To achieve that, you have different solutions:

  1. returning a promise: leave: (data) => Promise.resolve()
  2. using this.async() pattern: leave(data) { const done = this.async(); done(); } Of course those examples have no "duration", but they are asynchronous!!!

For the specific case of GSAP… This library is not promise-friendly. To know when you tween is complete, you need to use … onComplete. So the 2 above solutions become:

  1. Wrapping TweenMax inside a promise.
    leave(data) {
    return new Promise(resolve => {
    TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1, onComplete: resolve });
    });
    }
  2. Using this.async()
    leave(data) {
    const done = this.async();
    TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1, onComplete: done });
    }

Hope it will help you using barba.js :)

thierrymichel commented 5 years ago

Here you can find a codepen: https://codepen.io/thierrymichel/project/editor/XjzvdJ. The problem was identical. I forked it and … commented it. I think it can help you.

thierrymichel commented 5 years ago

One last thing… :) If I had to make your transition, my code will looks like this:

function titleLeave(e) {
   return new Promise(resolve => {
    TweenMax.fromTo(e, 2, { opacity: 1 }, { opacity: 0, onComplete: resolve });
  });
}

function titleEnter(e) {
  return new Promise(resolve => {
    TweenMax.fromTo(e, 2, { opacity: 0 }, { opacity: 1, onComplete: resolve });
  });
}

barba.init({
  transitions: [{
    sync: true,
    from: { namespace: 'home' },
    to: { namespace: 'about' },
    leave: (data) => titleLeave(data.current.querySelector('[data-title]'),
    // Same as above but we get destructured `next` directly from `data`
    enter: ({ next }) => titleEnter(next.querySelector('[data-title]'), 
  }],
});

Cheers

thierrymichel commented 5 years ago

A few additional thoughts…

const titleHome = document.querySelector('[data-title="home"]');
const titleAbout = document.querySelector('[data-title="about"]');
const titleBlog = document.querySelector('[data-title="blog"]');
const titleContact = document.querySelector('[data-title="contact"]');

This won't work. When loading one page and executing this code for the first time, you will not have access to all those titles. That's why I put the querySelector inside the transition hooks. Titles will be available through data.current or data.next.

On the modified example, I return titleLeave with the arrow function. As titleLeave returns a promise, I am fine.

leave: (data) => titleLeave(data.current.querySelector('[data-title]'),

is equivalent to

leave(data) {
  return titleLeave(data.current.querySelector('[data-title]');
},

I will close the issue. If you have any questions, feel free to join the Slack channel.

bdadev commented 5 years ago

Wow!!!!

First of all I'm sorry for making you waste your time in showing something very simple to understand. Seeing your answer I realized that I need and evolve in PROMISE, and yes, you were perfect in the explanation. I confess that I thought about 1000 times before creating this issue for fear of being misinterpreted. Now finally I understand that I have access to the elements in data.container and next, this was crucial to my understanding. I still have one question: Here the namespace is set just for home and about right?

barba.init({
  transitions: [{
    sync: true,
    from: { namespace: 'home' },
    to: { namespace: 'about' },
    leave: (data) => titleLeave(data.current.querySelector('[data-title]'),
    // Same as above but we get destructured `next` directly from `data`
    enter: ({ next }) => titleEnter(next.querySelector('[data-title]'), 
  }],
});

For other namespaces would I have to create another instance of barba.init()?

One last thing ...

Thanks again for your time and dedication in explaining the concept of this and I hope this can help others with the same difficulty as mine, and if this issue is harmful to the repository please feel free to delete ok?

A big hug!

P.S. When I finally have completed a site with beard.js I will be pleased to show you, with your permission of course.

thierrymichel commented 5 years ago

@bdadev you're welcome.

If you want different transitions, just add them to the transitions array option:

barba.init({
  transitions: [
    { name: 'first-transition', … },
    { name: 'second-transition', … },
    …,
  ],
  // Other options
});

If you want same transition for different namespaces, use an array:

barba.init({
  transitions: [{
    name: 'from-home-to-many', // Can be 'many-to-many', …
    from: { namespace: 'home' },
    to: { namespace: ['about', 'contact'],
    // Hooks…
  }],
  // Other options
});

And if you want same transition, everywhere, do not use any "rule"!

For every transition object, you can define rules to help barba.js choosing the right transition. Available rules are namespace, custom and route (with @barba/router). They can be used "alone" or coupled with from and/or to properties.

Check the documentation

In your case, if the user clicks -from- a page having "home" -namespace- ([data-barba-namespace="home"]) -to- another page with "about" -namespace- ([data-barba-namespace="about"]), barba will use the transition.

For the rest, it depends only on your content, ideas, creativity, user experience, … 😁

And, yes, if you used barba.js, please, send us a link! 👍

bdadev commented 5 years ago

Hey @thierrymichel!

I got from your example implement the transition and it worked very well.

I need more help if possible ....

I have a script that controls the scroll of the right page, more on the first page load the script is executed and after a transition it is no longer available or accessible and the function is not executed.

/* Bundle */
import {
    TimelineMax,
    CSSPlugin,
    AttrPlugin,
    TweenMax,
} from 'gsap/all';
import barba from '@barba/core';
import pageScroll from './components/page-scroll';

/* ./components/page-scroll.js */
const pageScroll = () => {
    const html = document.documentElement;
    const body = document.querySelector('body');

    const scroller = {
        target: document.querySelector('#scroll-container'),
        ease: 0.05, // <= scroll speed
        endY: 0,
        y: 0,
        resizeRequest: 1,
        scrollRequest: 0,
    };

    let requestId = null;

    TweenLite.set(scroller.target, {
        rotation: 0.01,
        force3D: true,
    });

    function updateScroller() {
        const resized = scroller.resizeRequest > 0;

        if (resized) {
            const height = scroller.target.clientHeight;
            body.style.height = `${height}px`;
            scroller.resizeRequest = 0;
        }

        const scrollY = window.pageYOffset || html.scrollTop || body.scrollTop || 0;

        scroller.endY = scrollY;
        scroller.y += (scrollY - scroller.y) * scroller.ease;

        if (Math.abs(scrollY - scroller.y) < 0.05 || resized) {
            scroller.y = scrollY;
            scroller.scrollRequest = 0;
        }

        TweenLite.set(scroller.target, {
            y: -scroller.y,
        });

        requestId = scroller.scrollRequest > 0 ? requestAnimationFrame(updateScroller) : null;
    }

    function onScroll() {
        scroller.scrollRequest++;
        if (!requestId) {
            requestId = requestAnimationFrame(updateScroller);
        }
    }

    function onResize() {
        scroller.resizeRequest++;
        if (!requestId) {
            requestId = requestAnimationFrame(updateScroller);
        }
    }

    function onLoad() {
        updateScroller();
        window.focus();
        window.addEventListener('resize', onResize);
        document.addEventListener('scroll', onScroll);
    }

    /* I believe the problem is here */
    window.addEventListener('load', onLoad);
};

export default pageScroll;

I believe the problem is here window.addEventListener('load', onLoad); maybe I should implement the call inside barba.init() . I really do not know how, sorry for this.

bdadev commented 5 years ago

One last thing… :)

After sending you the doubt I decided to test running the code on enter and it worked, but I found it a bit confusing as the code got more, it works.

// This returns a promise, we do not care about "context/arrow function"
enter: ({ current, next, trigger }) => {
    containerInAnim(next.container);
    TweenMax.to(document.querySelector('[data-layout="header"]'), 1, { y: '0%', });
    (() => {
        const html = document.documentElement;
        const body = document.querySelector('body');

        const scroller = {
            target: document.querySelector('#scroll-container'),
            ease: 0.05, // <= scroll speed
            endY: 0,
            y: 0,
            resizeRequest: 1,
            scrollRequest: 0,
        };

        let requestId = null;

        TweenLite.set(scroller.target, {
            rotation: 0.01,
            force3D: true,
        });

        function updateScroller() {
            const resized = scroller.resizeRequest > 0;

            if (resized) {
                const height = scroller.target.clientHeight;
                body.style.height = `${height}px`;
                scroller.resizeRequest = 0;
            }

            const scrollY = window.pageYOffset
                || html.scrollTop
                || body.scrollTop
                || 0;

            scroller.endY = scrollY;
            scroller.y
                += (scrollY - scroller.y) * scroller.ease;

            if (
                Math.abs(scrollY - scroller.y) < 0.05
                || resized
            ) {
                scroller.y = scrollY;
                scroller.scrollRequest = 0;
            }

            TweenLite.set(scroller.target, {
                y: -scroller.y,
            });

            requestId = scroller.scrollRequest > 0
                ? requestAnimationFrame(updateScroller)
                : null;
        }

        function onScroll() {
            scroller.scrollRequest++;
            if (!requestId) {
                requestId = requestAnimationFrame(
                    updateScroller,
                );
            }
        }

        function onResize() {
            scroller.resizeRequest++;
            if (!requestId) {
                requestId = requestAnimationFrame(
                    updateScroller,
                );
            }
        }

        function onLoad() {
            updateScroller();
            window.focus();
            window.addEventListener('resize', onResize);
            document.addEventListener('scroll', onScroll);
        }

        onLoad();
    })();
},
bdadev commented 5 years ago

Please, pretend that this question has never been asked because I already feel like a complete ass. I think age is affecting my intelligence.

I only thank God that there are people like you with the patience and discipline to answer questions as insignificant as this.

You are an incredible person and I am always grateful for your success.

thierrymichel commented 5 years ago

Hi,

If you need to execute code, depending on transition, for specific pages, use views.

barba.init({
  views: [{
    namespace: 'my-page',
    beforeEnter(data) { // Init your scroll stuff here… },
    afterEnter(data) { // or here… },
    beforeLeave(data) { // kill it here },
    afterLeave(data) { // or here… },
  }],
);

If you need to execute same code, after every transition, use a hook.

barba.hooks.before(data => {
  // this hook will be called before each transitions
});
barba.hooks.after(data => {
  // this hook will be called after each transitions
});
bdadev commented 5 years ago

Het @thierrymichel.

Thanks again for the great help. I thought you would have understood that there was no need to answer my last question, but in any case this helped a little more in my understanding and I was able to implement it in a complex way.

Thanks again for your patience.

Big hug!