Open der-lukas opened 5 years ago
Also interested in an example
I guess something like this: https://codesandbox.io/embed/locomotive-scroll-in-react-k0028.
It's throwing some console errors though but I just put this together really quickly. I've just attached a ref
to the container and the I fire the script via the useEffect
hook so it fires when the component is mounted.
@nathobson this doesn't work. Its not smooth.
I use a similar approach as @nathobson to get the element with aref
, but I use class components and initiate locomotiveScroll
in componentDidMount
.
https://codesandbox.io/s/proud-resonance-ou1rz
The main thing to just be aware of when using it with react is if you unmount the component that has rendered the DOM el
used by locomotiveScroll
, you'll need to destroy your locomotive scroll instance (i think in componentWillUnmount
), otherwise you will probably get a bunch of errors.
Hey, I'm trying to use the module using next.js, and I'm having trouble importing locomotiveScroll. I'm getting an error similar to the issue people had for nuxt.js, document is not defined, but somehow I couldn't find a way to make it work.
@cytronn You'll probably need to dynamically import the module so it's not processed server-side (no document
there).
@adamcoulombe how do you destroy it on componentWillUnmount?
@aaadamgo i guess use the destroy method
componentWillUnmount(){
myScroller.destroy()
}
@aaadamgo i guess use the destroy method
componentWillUnmount(){ myScroller.destroy() }
Thanks Adam, i tried that, or similar anyway. And when it changes page, if i go back to the page where I'm using it, something weird happens where it's likes there's 2 versions of the scrollbar.
see here:
export default class IndexPage extends React.Component {
constructor(props) {
super(props);
const lsRef = null;
}
render() {
return (
<Animation transitionStatus={this.props.transitionStatus}>
<div className="ls">
---
<Projects />
</div>
</Animation>
);
}
componentDidMount() {
this.lsRef = new LocomotiveScroll({
el: document.querySelector(".ls"),
smooth: true,
});
window.addEventListener("resize", () => {});
}
componentWillUnmount() {
this.lsRef.destroy();
}
}
Itβs possible that the destroy method only cleans up the events.
Take a look at the source:
Because of this you may also need to manually remove elements that were added like the scrollbar, using some vanilla JS to clean up DOM that locomotive is leaving behind.
Seems like locomotive should be doing this when you destroy- may be worth a separate issue/feature request- unless Iβm missing something?
Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:
import React from "react";
import dynamic from "next/dynamic";
const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), {
ssr: false
});
class Index extends React.Component {
componentDidMount() {
const scroll = new LocomotiveScroll({
el: document.querySelector(".app"),
smooth: true
});
console.log(scroll);
}
render() {
return (
<>
<div className="app">
....
</div>
</>
);
}
}
export default Index;
Thanks in advance!
Has anyone managed to get this to destroy completely.
@adamcoulombe i think i'm experiencing what you stated, when i leave the page with the locomotive-scroll on i'm calling destroy in a react hook but its not removing everything, which is fine on the page i go to but sometimes if i go back to a page with locomotive on it fucks up.
here is an example https://pup.adamwright90.now.sh/
Has anyone managed to get this to destroy completely.
@adamcoulombe i think i'm experiencing what you stated, when i leave the page with the locomotive-scroll on i'm calling destroy in a react hook but its not removing everything, which is fine on the page i go to but sometimes if i go back to a page with locomotive on it fucks up.
here is an example https://pup.adamwright90.now.sh/
Yeah, if you look at the code in the destroy function, it removes event listeners, but does no DOM cleanup whatsoever, which will likely cause issues
Hi, when I try to use Locomotive Scroll with React, it seems I can't see all my content. But if I resize the window, it works normally. Does someone have this issue too ? Thx, Laurie
@LaurieVince are you initializing the scroll in the componentDidMount
lifecycle method?
Sure, I do.
componentDidMount(){
const scroll = new LocomotiveScroll({
el: document.querySelector('.scroll'),
smooth: true
})
}
I note that it works perfectly when smooth is false. But when it's true, I can't see all my content. That's weird! Does someone else have this issue ?
Hi Laurie,
As @Jerek0 said in #66, you need to use update(). But another alternative, when you're using React : You simply can use the setTimeout method to call your scroll, in componentDidMount. Like this :
scroll() {
const scroll = new LocomotiveScroll({
el: document.getElementById('#scroll'),
smooth: true
});
}
componentDidMount(){
setTimeout(() => {
this.scroll();
}, 100);
}
I tested this, and it works perfectly for me.
@imcorentin π
If you have an incorrect scroll size (too long / short) at first state on your page: it probably comes from a layout change that occurred after LS init (e.g. an image loaded). Try resizing your window: it probably fixes the scroll.
To fix that for real, you need to call .update() on your LS instance after layout changes to refresh calculations.
In addition to that: 3.3.5 fixes an important issue with .update()
. Make sure to upgrade!
@imcorentin Hey! With Gatsby though, I'd tried a timeout on component mount/useEffect as well as a helper onInitialClientRender
which is triggered after the site is loaded, they didn't seem to be solving the issue. The workaround I found if the page has a lot of content loading is calling update()
at regular intervals.
@Jerek0 ahh maybe that update could fix the issue, will test it and get back.
Unfortunately, with the Gatsby case, it still seems like I have to call update()
on intervals to ensure that the scroll works correctly for all kinds of pages.
setInterval(() => scroll.update(), 1000);
Here is an updated example with 3.3.5 showing usage with react and using the update()
method when the route changes. I did find that you need to call it after a short timeout (100) sort of like @imcorentin pointed out
https://codesandbox.io/s/icy-wood-96gvz?fontsize=14&hidenavigation=1&theme=dark&view=preview
Unfortunately, with the Gatsby case, it still seems like I have to call
update()
on intervals to ensure that the scroll works correctly for all kinds of pages.setInterval(() => scroll.update(), 1000);
The only thing about calling it repeatedly on interval, is that you have smooth:true
, it may make it a little clunky if it updates mid-scroll, it would stop the lerping
Hey everyone! I have some troubles when using scrollTo method. I have a main with data-scroll-section which contains 5 sections. Everything works great but when I fire a scrollTo the first section (main's first child), the main doesn't get the new position of the main, so i cannot scroll up after fire a scrollTo.
Also, I cannot access update() method due to react : I create the locomotiveScroll in a use effect.
Is someone have any idea? Many thanks :)
Hey @mehdilouraoui, can you please create a Codesandbox to show us your issue with the scrollTo method?
Hey @imcorentin I just recreate it (very simpler than the current one in my project): https://codesandbox.io/s/determined-lehmann-w5876
And it works... but not in my project. I attach useState hook to GridInterior component just because I fetch content when the component is mounted.
However, instead of attach the scrollTo to a forwardRef, i just attach it to a div which wrap the component, but nothing works.
I'm not sure this is pretty clear, I just have to investigate more in my project.
Unfortunately, with the Gatsby case, it still seems like I have to call
update()
on intervals to ensure that the scroll works correctly for all kinds of pages.setInterval(() => scroll.update(), 1000);
I had this issue too and also ran into the issue that @adamcoulombe addressed from your reply.
Depending on what kind of site you have, this might work (it did for me). I was able to insert this scroll method into the componentDidMount
lifecycle method and then added a slight delay to the update method.
componentDidMount() {
this.scroll = new LocomotiveScroll({
el: document.querySelector("body"),
smooth: true,
})
setTimeout(() => this.scroll.update(), 300);
}
I then just destroy the scroll instance and do it on separate pages. If you have dynamic content coming in, (maybe a table that is generated through an AJAX request) this might not work for you, but it worked for me.
Hey @elebumm ! Yes it's a solution but I don't want to fix with a timeout because as you said, I fetch content from an API. I'm currently trying to update the locomotive scroll (with a boolean in a state) once the content is fully loaded. Should works
How to implement it in Gatsby ?
Hello all,
Thank you for this feed :) Regarding LM on Wordpress I did this:
At the end of my preloader I call a callback with an onComplete: scroll.update ();
To always have the right height, I had woocommerce pages that scrolled the content after my footer.
And during the transition with barbajs I destroy LM at hooks.beforeLeave and I reinit it at the entrance of the new DOM .hooks.after
Hi, I currently have Locomotive Scroll working in Gatsby, I have some snippets down below.
But I'm posting on here regarding the data-scroll-call
and more specifically to figure out how to use it with Gatsby.
I basically init the library on the client and store the instance in
window
. I can then simply do stuff like updating the scroll by calling() => typeof window !== 'undefined' && window.scroll.update()
.
window.scroll.on('call', func => {
func === 'testCall' && console.log('Event Called')
})
I get an error β οΈ TypeError: "window.scroll.on is not a function"
import LocomotiveScroll from 'locomotive-scroll'
//
useEffect(() => {
let locomotiveScroll
locomotiveScroll = new LocomotiveScroll({
el: document.querySelector('#___gatsby),
...scroll.options,
})
locomotiveScroll.update()
// Exposing to the global scope
window.scroll = locomotiveScroll
return () => {
if (locomotiveScroll) locomotiveScroll.destroy()
}
}, [location])
// Callback on routeChange
gatsby-node.js
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === 'build-html' || stage === 'develop-html') {
actions.setWebpackConfig({
module: {
rules: [
{
test: /locomotive-scroll/,
use: loaders.null(),
},
],
},
})
}
}
π Now it works and you can reference the instance like so () => typeof window !== 'undefined' && window.scroll.update()
.
Hey @fcisio, did you try this (instead of window.scroll
)?
useEffect(() => {
const locomotiveScroll = new LocomotiveScroll({
el: document.querySelector(myScrollContainer)
})
locomotiveScroll.on('call', value => {
value === 'testCall' && console.log('Scroll function called');
});
}, [])
Hi, I currently have Locomotive Scroll working in Gatsby, I have some snippets down below.
But I'm posting on here regarding the
data-scroll-call
and more specifically to figure out how to use it with Gatsby.I basically init the library on the client and store the instance in
window
. I can then simply do stuff like updating the scroll by calling() => typeof window !== 'undefined' && window.scroll.update()
.Here's my issue, when trying to call an event inside a component:
window.scroll.on('call', func => { func === 'testCall' && console.log('Event Called') })
I get an error β οΈ
TypeError: "window.scroll.on is not a function"
Any pointers?
My working Gatsby config
import LocomotiveScroll from 'locomotive-scroll' // useEffect(() => { let locomotiveScroll locomotiveScroll = new LocomotiveScroll({ el: document.querySelector('#___gatsby), ...scroll.options, }) locomotiveScroll.update() // Exposing to the global scope window.scroll = locomotiveScroll return () => { if (locomotiveScroll) locomotiveScroll.destroy() } }, [location]) // Callback on routeChange
In
gatsby-node.js
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => { if (stage === 'build-html' || stage === 'develop-html') { actions.setWebpackConfig({ module: { rules: [ { test: /locomotive-scroll/, use: loaders.null(), }, ], }, }) } }
π Now it works and you can reference the instance like so
() => typeof window !== 'undefined' && window.scroll.update()
.
I can't get your config files to work, do you mind sharing the files?
Does anyone here have a repo with a working Gatsby example? I can get it working fine but everything messes up when I navigate to a new page. Thank you!
Does anyone here have a repo with a working Gatsby example? I can get it working fine but everything messes up when I navigate to a new page. Thank you!
Hi, I have just put together a repo, I hope this can help many people π
Hey @fcisio this works absolutely amazingly, thank you so much! The only thing i've found is when using the back and forward functions of the browser, it doesn't seem to refresh the container heights for locomotive, do you have any idea how you would add that functionality? Thanks again
Hmm actually ignore me sorry @fcisio! This seems to be an issue with when i'm using leave animation transitions with Framer Motion... trying to figure it out now. Thanks again :)
@fcisio Thank you so much, it work like a charm!
@fcisio hey, I used your code, but, it doesn't work on my page which has several gsap timelines... it stucks at the top of the page, unable to scroll and timeline not working with smooth=true though I added updateScroll as following:
const updateScroll = () => isBrowser && window.scroll.update()
.
.
.
.
.
gsap.registerPlugin(ScrollTrigger)
const tl = gsap.timeline({
scrollTrigger: {
start: 'top -20',
},
})
gsap.from('.about-paragraphs #para1', vars)
tl.from('.about-paragraphs #para2', vars)
.from('.about-paragraphs #para3', vars)
.from('.about-paragraphs #para4', vars)
.call(() => updateScroll())
I know I'm wrong w sth, I don't know what!
Hi @Arshazar
Using smooth: true
means that native scroll will not be used. Instead, a container element is animated to simulate the scroll behavior.
I believe the issue is not the timelines themselves, but the use of scrollTrigger
which I think will only work with native scroll.
In this scenario, you will only be able to use one or the other. I hope I was able to answer your question.
@fcisio Thank u, mate!
@Arshazar and @fcisio fyi, you can integrate both. Take a look here
Hope this helps π
Hey @edoardolunardi , I actually wanted to do so, but I assumed it'd be a headache to try! Anyway, it really helped! thank you...
Here is an updated example with 3.3.5 showing usage with react and using the
update()
method when the route changes. I did find that you need to call it after a short timeout (100) sort of like @imcorentin pointed outhttps://codesandbox.io/s/icy-wood-96gvz?fontsize=14&hidenavigation=1&theme=dark&view=preview
Here is a hooks implementation of this same thing https://codesandbox.io/s/nostalgic-cannon-7tk08?file=/src/App.js
Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:
import React from "react"; import dynamic from "next/dynamic"; const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), { ssr: false }); class Index extends React.Component { componentDidMount() { const scroll = new LocomotiveScroll({ el: document.querySelector(".app"), smooth: true }); console.log(scroll); } render() { return ( <> <div className="app"> .... </div> </> ); } } export default Index;
Thanks in advance!
did you get it to work?
Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:
import React from "react"; import dynamic from "next/dynamic"; const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), { ssr: false }); class Index extends React.Component { componentDidMount() { const scroll = new LocomotiveScroll({ el: document.querySelector(".app"), smooth: true }); console.log(scroll); } render() { return ( <> <div className="app"> .... </div> </> ); } } export default Index;
Thanks in advance!
did you get it to work?
π NextJS dynamic import is used to dynamically import React components, not classes. This way you will need to load it like <LocomotiveScroll />
in your code.
Succeed to get it working with NextJS doing like this :
import React, { createContext, useEffect, useState } from 'react'
import { Scroll, LocomotiveScrollOptions } from 'locomotive-scroll'
interface ContextProps {
scroll: Scroll | null
}
export const SmoothScrollContext = createContext<ContextProps>({
scroll: null,
})
interface Props {
options: LocomotiveScrollOptions
}
export const SmoothScrollProvider: React.FC<Props> = ({ children, options }) => {
const [scroll, setScroll] = useState<Scroll | null>(null)
useEffect(() => {
;(async () => {
try {
const LocomotiveScroll = (await import('locomotive-scroll')).default
setScroll(
new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]') ?? undefined,
...options,
})
)
} catch (error) {
throw Error(`[SmoothScrollProvider]: ${error}`)
}
})()
return () => {
scroll?.destroy()
}
}, [])
return <SmoothScrollContext.Provider value={{ scroll }}>{children}</SmoothScrollContext.Provider>
}
SmoothScrollContext.displayName = 'SmoothScrollContext'
SmoothScrollProvider.displayName = 'SmoothScrollProvider'
I'm using a context here for being able to access to the instance on all my childs components.
π¨ Don't forget to use data-scroll-section
or you will face weird behaviours.
Give me a bit of time so I will wrap all of this on an NPM package.
Types coming from : Link
Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:
import React from "react"; import dynamic from "next/dynamic"; const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), { ssr: false }); class Index extends React.Component { componentDidMount() { const scroll = new LocomotiveScroll({ el: document.querySelector(".app"), smooth: true }); console.log(scroll); } render() { return ( <> <div className="app"> .... </div> </> ); } } export default Index;
Thanks in advance!
did you get it to work?
π NextJS dynamic import is used to dynamically import React components, not classes. This way you will need to load it like
<LocomotiveScroll />
in your code.Succeed to get it working with NextJS doing like this :
import React, { createContext, useEffect, useState } from 'react' import { Scroll, LocomotiveScrollOptions } from 'locomotive-scroll' interface ContextProps { scroll: Scroll | null } export const SmoothScrollContext = createContext<ContextProps>({ scroll: null, }) interface Props { options: LocomotiveScrollOptions } export const SmoothScrollProvider: React.FC<Props> = ({ children, options }) => { const [scroll, setScroll] = useState<Scroll | null>(null) useEffect(() => { ;(async () => { try { const LocomotiveScroll = (await import('locomotive-scroll')).default setScroll( new LocomotiveScroll({ el: document.querySelector('[data-scroll-container]') ?? undefined, ...options, }) ) } catch (error) { throw Error(`[SmoothScrollProvider]: ${error}`) } })() return () => { scroll?.destroy() } }, []) return <SmoothScrollContext.Provider value={{ scroll }}>{children}</SmoothScrollContext.Provider> } SmoothScrollContext.displayName = 'SmoothScrollContext' SmoothScrollProvider.displayName = 'SmoothScrollProvider'
I'm using a context here for being able to access to the instance on all my childs components. π¨ Don't forget to use
data-scroll-section
or you will face weird behaviours. Give me a bit of time so I will wrap all of this on an NPM package.Types coming from : Link
It would be much better if you provide working example. Do you have any ?
Hello. I am using Next.js and just did what @millette said to dynamically import the module. The error is gone (document is not defined) but still doesn't work. Did I miss something? Here's my code:
import React from "react"; import dynamic from "next/dynamic"; const LocomotiveScroll = dynamic(() => import("locomotive-scroll"), { ssr: false }); class Index extends React.Component { componentDidMount() { const scroll = new LocomotiveScroll({ el: document.querySelector(".app"), smooth: true }); console.log(scroll); } render() { return ( <> <div className="app"> .... </div> </> ); } } export default Index;
Thanks in advance!
did you get it to work?
π NextJS dynamic import is used to dynamically import React components, not classes. This way you will need to load it like
<LocomotiveScroll />
in your code. Succeed to get it working with NextJS doing like this :import React, { createContext, useEffect, useState } from 'react' import { Scroll, LocomotiveScrollOptions } from 'locomotive-scroll' interface ContextProps { scroll: Scroll | null } export const SmoothScrollContext = createContext<ContextProps>({ scroll: null, }) interface Props { options: LocomotiveScrollOptions } export const SmoothScrollProvider: React.FC<Props> = ({ children, options }) => { const [scroll, setScroll] = useState<Scroll | null>(null) useEffect(() => { ;(async () => { try { const LocomotiveScroll = (await import('locomotive-scroll')).default setScroll( new LocomotiveScroll({ el: document.querySelector('[data-scroll-container]') ?? undefined, ...options, }) ) } catch (error) { throw Error(`[SmoothScrollProvider]: ${error}`) } })() return () => { scroll?.destroy() } }, []) return <SmoothScrollContext.Provider value={{ scroll }}>{children}</SmoothScrollContext.Provider> } SmoothScrollContext.displayName = 'SmoothScrollContext' SmoothScrollProvider.displayName = 'SmoothScrollProvider'
I'm using a context here for being able to access to the instance on all my childs components. π¨ Don't forget to use
data-scroll-section
or you will face weird behaviours. Give me a bit of time so I will wrap all of this on an NPM package. Types coming from : LinkIt would be much better if you provide working example. Do you have any ?
Sandbox link here : https://codesandbox.io/s/nextjs-with-locomotive-scroll-nl7vs π
@toinelin This works fine on a single page but as soon as you route to other pages it falls apart for me. Since scroll
is not in your effect dependencies, it will be always null
which makes destroy
undefined all the time, hence why you end up with multiple instances when you start to navigate around the page.
return () => {
console.log(scroll); // this is always null
scroll && scroll.destroy()
}
I got much better results by moving the Provider to _app
and trying to call update
on the scroller on route changes but I get issues with height calculation there, when rendering is not done before update is called.
Also tried periodically calling update
but that makes scrolling not so smooth anymore.
@mxmtsk Good point for the useEffect
the scroll instance on the return still the same as the default one from context which is null
before the client is ready and not updated by the assignation, I missed that. But as far as I understand, the scroll
instance should be overwriting itself each time you change a page and not colliding itself in any ways (which is not what we want too obviously). One solution I see to properly ensure that the instance is destroyed each time you leave the page could be to add scroll
as effect dependency and check if scroll
is null
before calling a new instance to avoid unwanted loop crashes. I did not explore as far the solution with the Provider on _app
as I wanted something possibly modular between pages. For the height calculation, did you try using useLayoutEffect
instead of useEffect
? Omh, I had issues and weird behaviours by forgetting to add data-scroll-section
on each pages, if it could be a trail for you to explore
I updated the sandbox with a new page if you want to try it.
@toinelin Sure, if you have time to provide a working example with two pages it would help. I would try it out in our "real world" page with complex page structure immediately. Would love to find a robust implementation for locomotive scroll in next.js :)
Is there a way of using this library with React? How would I initialize and target components? Would be awesome if you could supply a minimal setup example!
Thank you!