jlmakes / scrollreveal

Animate elements as they scroll into view.
https://scrollrevealjs.org/
22.39k stars 2.26k forks source link

Reveal from bottom on scroll down and from top on scroll up #384

Closed antoniocosta closed 7 years ago

antoniocosta commented 7 years ago

Is it possible? I have been fiddling but can't find a way to do it. I am guessing origin or distance would have to be reversed depending on scroll direction? Please let me know... and thanks so much for the great work put into this!

jlmakes commented 7 years ago

Hey Antonio,

ScrollReveal presently has no built-in awareness of scroll direction. I think options.scale is the closest approximation of getting elements to reveal "from the top on scroll up, from the bottom on scroll down". (Demo)

For what it's worth, I can point you to the correct part of the source to begin adding this functionality if you'd like to take a stab at forking the repository and creating a custom build.

antoniocosta commented 7 years ago

Hi Julian, Yeah... I noticed you achieve similar effect with scale. Scaling doesn't look that great when there's text though. Yes... please do point me to the correct part of the source and I'll take a stab at it! I think it's worth a shot. Here's roughly what I am trying to do: https://jsfiddle.net/abmc/9d0qpsb0/

antoniocosta commented 7 years ago

PS: On a slightly separate note... too bad passing scale: "1, .5" for only Y scaling doesn't work anymore on the new beta.

jlmakes commented 7 years ago

I can see why you want this behavior, the end result should look awesome.

Edit: This is now baked-in since beta.20

~Tracking Scroll Direction.~

~You’ll want to start right here:~ ~/src/instance/functions/delegate.js on line 16~

I'd guess that you probably only have one container, but regardless, they are all stored within the object ScrollReveal().store.containers; each one will end up looking something like this:

{
    id: 2,
    node: <html>,
    geometry: {
        bounds: { top: 0, right: 420, bottom: 1446, left: 0 },
        height: 1446,
        width: 420,
    },
    scroll: { top: 360, left: 0 },
    direction: { x: 0, y: -1 }, // this is new!
}

Where 1 indicates scrolling down/right, and -1 indicates scrolling up/left.

jlmakes commented 7 years ago

Applying The Right Animations

This is the part I think it gets complicated to bake into ScrollReveal. I think instead, you'd want to use ScrollReveal just to track visibility and provide callbacks... but not to create/apply the styles automatically.

Here is one way to do that:

var noEffect = {
    delay: 0,
    distance: '0',
    duration: 0,
    easing: 'linear',
    opacity: null,
    origin: 'bottom',
    rotate: { x: 0, y: 0, z: 0, },
    scale: 1,
    // container: this.defaults.container,
    // desktop: this.defaults.desktop,
    // mobile: this.defaults.mobile,
    reset: true,
    useDelay: 'always',
    // viewFactor: this.defaults.viewFactor,
    // viewOffset: this.defaults.viewOffset,

    afterReset: function (el) {},
    afterReveal: function (el) {},
    beforeReset: function (el) {}, // fill me in
    beforeReveal: function (el) {}, // fill me in
}

ScrollReveal().reveal('.items', noEffect)

Edit: The use of this here was a mistake in this pseudo code; these lines should be removed, or updated to use ScrollReveal() instead of this to prevent errors, e.g. ScrollReveal().defaults.container.

. . .

This reveal() effectively won't do anything, but it does give you access to callbacks.

If you create your own CSS animation/transition classes, you can apply them based on the container's direction property. Here’s an example of how you might implement those callbacks:

var beforeRevealCallback = function (el) {
    var id = el.getAttribute('data-sr-id')
    var containerId = ScrollReveal().store.elements[id].containerId
    var container = ScrollReveal().store.containers[containerId]

    if (container.direction && container.direction.y > 0) {
        el.classList.add('animateFromBottom')
    } else {
        el.classList.add('animateFromTop')
    }
}

var beforeResetCallback = function (el) {
    el.classList.remove('animateFromBottom')
    el.classList.remove('animateFromTop')
}
jlmakes commented 7 years ago

Now, I haven't tested this implementation all the way through, but this should hopefully give you a solid start at achieving the effect you'd like.

PS: On a slightly separate note... too bad passing scale: "1, .5" for only Y scaling doesn't work anymore on the new beta.

This is because instead of passing options.scale into a template string like "scale(" + options.scale + ")", ScrollReveal 4 generates CSS Transform matrices—so that hack no longer works 😉

If you'd like, you can create a new issue to request that feature be added. If it can garner some community support, it could be possible to add support for something like options.scale = [1, 0.5].

sebszocinski commented 6 years ago

Hey guys, I've been looking for the same thing, and have tried your code above @jlmakes and noticed a small bug which was forcing it to always add the class of animateFromTop but the fix is...

if (container.direction > 0) { should be if (container.direction.y > 0) {

This works, however on first load before you scroll I get:

Cannot read property 'y' of undefined

jlmakes commented 6 years ago

@sebszocinski Nice catch. I updated the pseudo code with:

if (container.direction && container.direction.y > 0) {
    el.classList.add('animateFromBottom')
} else {
    el.classList.add('animateFromTop')
}

Also, I've taking a second look at the commit though, and I'm not sure why container.direction is assigned before the container scroll is captured (causing it to be undefined on first load).

I'll report back with why, or bump a new version with the fix.

antoniocosta commented 6 years ago

Whoa!!! :) Here's my stab at that pseudocode: https://jsfiddle.net/abmc/oaxmma6y/ Just had to comment all the this.defaults.* props to get rid of undefined errors.

jlmakes commented 6 years ago

@antoniocosta Nice!

You're totally right about the undefined errors with this, my brain done did a bad. I updated my earlier comment with a note on those erroneous lines, and how to fix them.

rh-erivera commented 5 years ago

Hey guys, Ive been able to achieve this affect with the following code:

block1MedImgLeft()

function block1MedImgLeft() {
  ScrollReveal().reveal('#block1-medImgLeft', { origin: 'bottom',
    afterReveal: function () {
      ScrollReveal().reveal('#block1-medImgLeft', { origin: 'top',
        afterReveal: block1MedImgLeft
      })
    }
  })
}

This assumes the user will scroll down past #block1-medImgLeft and then scroll up again. This is not a perfect solution so I am wondering if there has been any headway on this functionality. I am using this code for a SPA and would like to add the scroll effect to all elements that are dynamically generated. This means writing a function like the one above for every element with a unique Id. Any ideas on a better way to achieve this affect and apply it to all the generated elements? Thanks!

eiranix commented 3 years ago

@jlmakes Another refinement to your if statement there is to make it pick the correct direction on first load:

if (container.direction) {
    if (container.direction.y > 0) {
        el.classList.add('animateFromBottom');
    } else {
        el.classList.add('animateFromTop');
    };
} else {
    if(el.getBoundingClientRect().top > 0) {
        el.classList.add('animateFromBottom');
    } else {
        el.classList.add('animateFromTop');
    };
};
BenRacicot commented 2 years ago

This is awesome! Anyone have a working example? Could we reopen this to discuss adding a directional argument for the origin option?

antoniocosta commented 2 years ago

Quite old but try this example: https://jsfiddle.net/abmc/oaxmma6y/