Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll Progress (set) #26

Closed
davidjerleke opened this issue Oct 6, 2019 · 25 comments
Closed

Scroll Progress (set) #26

davidjerleke opened this issue Oct 6, 2019 · 25 comments
Labels
feature request New feature or request resolved This issue is resolved

Comments

@davidjerleke
Copy link
Owner

davidjerleke commented Oct 6, 2019

👉 Feature Specification

// set scroll progress
// accepts a number from 0 - 1 representing % scrolled of scrollable distance
// scroll to target is smooth

embla.scrollToProgress(0.5)

As discussed in issue #21.

Special thanks

@xiel for this feature request.

@davidjerleke davidjerleke added feature request New feature or request discussion This is a conversation or discussion about something labels Oct 6, 2019
@davidjerleke davidjerleke removed the discussion This is a conversation or discussion about something label Dec 4, 2019
@davidjerleke
Copy link
Owner Author

davidjerleke commented Feb 4, 2020

Hi Felix (@xiel),

I'm currently investigating this feature and I'd like to discuss it further with you.

This feature mainly raises one concern for me. I recently implemented scrollBy(), and every time the user calls it Embla checks what the target snap index will be. This is not an expensive approach since scrollBy() is smooth and it doesn't make sense to call it very fast repeatedly.

But I imagine that setting scroll progress instantly means that users may use this feature to update it quite fast and repeatedly, by connecting their own alternative input or similar. This may lead to performance issues if Embla has to check the target snap index in an intense manner (like 100 times in a second or similar).

Any thoughts regarding this?

Kindly,
David

@davidjerleke davidjerleke added the awaiting response Issue is awaiting feedback label Feb 7, 2020
@davidjerleke davidjerleke mentioned this issue Feb 10, 2020
@xiel
Copy link
Contributor

xiel commented Feb 10, 2020

To be honest I think both methods should support being set frequently (every frame) & hard.

Calculating the snap point should not be a very expensive calculation when the layout & snap points stayed the same?

@davidjerleke
Copy link
Owner Author

davidjerleke commented Feb 13, 2020

Thanks for your input Felix (@xiel),

Well yes, maybe it's not an expensive calculation. But there is another thing to consider. Setting this frequently (every frame) from let's say snap index 0 to 4 will trigger the select event four times which could be pretty annoying if you're listening to the select event:

  • 0 --> 1
  • 1 --> 2
  • 2 --> 3
  • 3 --> 4

Another question is how we should solve this when loop is enabled and scroll is smooth. Should the carousel seek the closest way to the target (given scroll progress), like how scrollTo() works? For example, if current scroll progress is 0.9 and the user calls embla.scrollToProgress(0.1), this would scroll forward instead of backward?

Thanks in advance,
David

@xiel
Copy link
Contributor

xiel commented Feb 19, 2020

  1. The same would happen if I would drag it I assume? If I use the select listener to update a pagination or similar, I would want to update it as soon as the index changes, even during the drag/animation process. The dev can always debounce if he is only interested in less frequent updates I would say?

  2. I would not make it scroll forward automatically when you call 0.1. Instead I would allow to set 1.1 (% 1) to set 0.1 forward if wanted by the dev in loop mode.

@davidjerleke davidjerleke removed the awaiting response Issue is awaiting feedback label Feb 23, 2020
@davidjerleke
Copy link
Owner Author

Thanks for your input Felix (@xiel). I'll consider your thoughts and input when implementing this feature. I'm looking into this so I'll let you know when I have something.

Best
David

@davidjerleke davidjerleke added upcoming A feature or bug fix is on its way for this issue resolved This issue is resolved and removed upcoming A feature or bug fix is on its way for this issue labels Apr 15, 2020
@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 21, 2020

Hello Felix (@xiel),

I'm excited to announce release v2.9.1 which comes with a new set of utilities to manipulate the current scroll progress. Please read the release description for further details 🎉.

Just as an example, it's now possible for a carousel to imitate another carousels progress like so:

carouselOne.on('select', () => {
  const target = true
  const carouselOneProgressTarget = carouselOne.scrollProgress(target) // boolean true gets the target scroll progress instead of current scroll progress
  carouselTwo.scrollToProgress(carouselOneProgressTarget)
}

I'd very much appreciate if you could confirm that it's working as expected.

Cheers!
David

@davidjerleke davidjerleke added the awaiting response Issue is awaiting feedback label Apr 21, 2020
@xiel
Copy link
Contributor

xiel commented Apr 23, 2020

Can you explain what is the difference between target and not target position in scrollProgress(true/false). Not very clear to me atm, when are the numbers different?

Everything looks great, thanks so much!

@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 23, 2020

Thanks @xiel for the feedback. So the difference is:

Location progress:
Returns the carousels scroll progress for its current scroll location. For example, when the carousel has scrolled half its scrollable length, it will return 0.5.

Target progress:
Returns the carousels scroll progress for its destination. For example, if the user does something that sets the carousel in motion, like drags it or clicks on a navigation button, the carousel will start scrolling towards a target. This returns the progress to where the carousel is headed, and not where it's right now. So let's say the carousel is on the first snap, and the user clicks to navigate to the last snap, this will return 1 (which is max scroll progress) even though the carousel hasn't reached its target yet.

Is this explanation better? Let me know if this makes sense 🙂.

Cheers!

@xiel
Copy link
Contributor

xiel commented Apr 23, 2020

@davidjerleke Ah yes, totally makes sense! Thanks!

What do you think would be the best way to update the scrollPos by px values now?
Me might also need an API to read the current scrollLength then?

For example, I know I want to move 20px from current position, how can I translate that to the percent I need for scrollBy(x). I would need to have 20 / scrollLength?
I think it would be more efficient if I was able to read that from embla, than reading layout myself.

Background
I'm currently working on a small lib that normalizes wheel events and does momentum detection for a better UX (inertia scolling in macOS/Win10), and I would like to basically add fine grained control over the embla carousel using wheel events of mouse and trackpad (#47).
As not everyone will want wheel events, I thought I might offer this as a wrapper/add-on/different package later on to you, so only people wanting it, would get the extra ~2kb needed.

@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 23, 2020

Ah I see. I'm going to add the wheel scrolling feature to Embla as discussed in issue #47 as soon as possible. This will be an option and not mandatory. Will this help your case? Or am I misunderstanding you maybe?

@xiel
Copy link
Contributor

xiel commented Apr 23, 2020

I'm basically offering you, to basically take over implementing issue #47 ;)
But if you prefer to do it yourself that is fine too of course.

@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 23, 2020

I understand! That would be awesome @xiel 🙂.

I've had this "crazy" idea for a while now, about exposing the Embla engine with all its internal utils like so:

embla.dangerouslyGetEngine()

But I'm afraid the lib will drown in issues if I do this 🙈. But maybe it's just enough to expose the internal pxToPercent() utility and percentToProgress() then?

@xiel
Copy link
Contributor

xiel commented Apr 23, 2020

For future extensibility exposing the full engine would surely be a bit more dangerous, but a good choice IMHO. I think it would be a great way to able to add functionality via plugins/wrappers which could be installed separately, so the main package stays small and tidy.

@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 23, 2020

Thanks for the feedback @xiel. Appreciate your thoughts and efforts 👍. Do you think I should add the engine exposure to the README? Because doing so might lead to a lot of issues about how to achieve this and that. Or do you want me to just do it and send you the info?

@xiel
Copy link
Contributor

xiel commented Apr 23, 2020

I think everyone who wants to use the engine directly needs to dive a bit into the code anyway, so I don't think there is a need to add lot of documentation for it :)

Happy to get it via mail or it could be a small section in CONTRIBUTING.md or another extra file?

@davidjerleke
Copy link
Owner Author

Thanks! I'll let you know when I have something.

Cheers!
David

@davidjerleke davidjerleke removed the awaiting response Issue is awaiting feedback label Apr 29, 2020
@davidjerleke
Copy link
Owner Author

Hi Felix (@xiel),

I'm preparing for the release of Embla v.3, but this might take a week or so to finish, so I've decided to commit and push the api method that exposes the engine, and I've done so in this commit. So if you clone the repo you should be able to make use of:

const engine = embla.dangerouslyGetEngine()

...now 🙂. For anyone building an extension package for Embla I'd be very happy to help out with how the engine works. So feel free to ask questions if you're building the scroll wheel package.

With the engine exposed, you won't need scrollBy or scrollProgress at all to achieve what you want. Embla runs an animation loop which renders updates with requestAnimationFrame. And by updating its target and starting the loop, Embla will seek it's target with attraction physics. These methods should help you:

// Grab engine
const engine = embla.dangerouslyGetEngine()

// When wheel starts scrolling you can do this
engine.scrollBody.useSpeed(80) // This attraction is so much that it will almost move instantly
engine.animation.start()

// Update target so attraction kicks in and moves the carousel towards its target
const pxToScrollBy = wheelDeltaInPx 
const percentToScrollBy = engine.pxToPercent(pxToScrollBy) // Convert px to percent
engine.target.add(percentToScrollBy)

Please take a closer look at src/components/dragHandler.ts. Maybe it will help you forward 😃.
Best of luck and let me know if you need further info!

Kindly
David

@xiel
Copy link
Contributor

xiel commented May 1, 2020

@davidjerleke Perfect thanks! I'll let you know when I have something to show or questions ;)

@phmasek
Copy link

phmasek commented Dec 20, 2021

@davidjerleke amazing job with the library! Using it in several projects at the moment.

Just wanted to check if the function scrollToProgress() was removed in the latest version or just moved? I'm keen to sync the carousel with window scroll in an easy way.

@davidjerleke
Copy link
Owner Author

@phmasek thanks.

The CodeSandbox in this comment might help you achieve what you want.

Yes, scrollToProgress() was removed in v3. Release details here.

Please note that you need to change all calls to embla.dangerouslyGetEngine() to embla.internalEngine() if you’re using v6 and up. Read more about all breaking changes in the release details.

Best
David

@phmasek
Copy link

phmasek commented Dec 20, 2021

Perfect, thank you! I created a callback function based on your CodeSandbox that could easily be used for connecting scroll.

const scrollToProgress = React.useCallback(
  (progress: number) => {
    if (!embla) return;

    const { limit, target, scrollProgress, scrollBody, scrollTo } = embla.internalEngine();
    const currentProgress = scrollProgress.get(target.get());
    const allowedProgress = Math.min(Math.max(progress, 0), 1);
    const progressToTarget = allowedProgress - currentProgress;
    const distance = progressToTarget * limit.length * -1;

    scrollBody.useBaseMass().useBaseSpeed();
    scrollTo.distance(distance, false);
  },
  [embla]
);

I know it's suboptimal considering that the library moved towards a plugins paradigm. Hopefully I'll get the time to contribute with a plugin. But until then the callback might be useful for someone..

@alex-major-digital
Copy link

Is there anyway to achieve this in the latest version of Embla? I'm looking to scroll one carousel, and have the relative scroll progress update another carousel.

Hello Felix (@xiel),

I'm excited to announce release v2.9.1 which comes with a new set of utilities to manipulate the current scroll progress. Please read the release description for further details 🎉.

Just as an example, it's now possible for a carousel to imitate another carousels progress like so:

carouselOne.on('select', () => {
  const target = true
  const carouselOneProgressTarget = carouselOne.scrollProgress(target) // boolean true gets the target scroll progress instead of current scroll progress
  carouselTwo.scrollToProgress(carouselOneProgressTarget)
}

I'd very much appreciate if you could confirm that it's working as expected.

Cheers! David

@davidjerleke
Copy link
Owner Author

Hi @alex-major-digital,

Did you check this comment out? And this comment could also be helpful.

Best,
David

@alex-major-digital
Copy link

alex-major-digital commented Apr 27, 2023

Hi David (@davidjerleke),

I did, and they were helpful - I've got carousel A scrolling carousel B simultaneously.

I'm having an issue scrolling carousel B to the relative amount of carousel A, as the carousels are different heights (scrolling on the Y axis).

For example, when carousel A has slide index 2 aligned in the center, then carousel B should also have it's slide index 2 aligned to the center too.

I also need to figure how to stop scrolling the carousels when they hit their boundarys.

Any help would be greatly appreciated.

Thanks,
Alex

@davidjerleke
Copy link
Owner Author

davidjerleke commented Apr 27, 2023

Hi @alex-major-digital,

Oh yeah. Embla has changed to px instead of % since that example was created. That’s why you’re experiencing problems. I guess you could use the percentOfView method on the internalEngine to figure that out.

I’m very busy with the upcoming version 8 so unfortunately I won’t be able to help you soon.

Best,
David

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request resolved This issue is resolved
Projects
None yet
Development

No branches or pull requests

4 participants