When I use jQuery to animate the scrolling behavior the first click will cause the view to jump straight to the clicked anchor element. The second click, if issued within the animation-delay, will work flawlessly. If the animation-delay runs out and one issues a click again, it'll cause a straight jump to the element again.
Here's a short gif demonstrating the issue:
As you can see, I click on services
and instantly jump. I wait until the animation delay runs off and click portfolio
, again an instant jump. But then I quickly click services
again (animation delay didn't run off yet) and it works as expected.
My Elm-Application has onClick
events assigned to some elements. When triggered, the href
of the element gets passed to a JavaScript function through a port, like so:
-- partial view
view =
div []
[ nav [ class "navbar navbar-default navbar-fixed-top" ]
[ div [ class "container" ]
[ div [ class "navbar-header page-scroll" ]
[ button [ class "navbar-toggle", attribute "data-target" "#bs-example-navbar-collapse-1", attribute "data-toggle" "collapse", type' "button" ]
[ span [ class "sr-only" ]
[ text "Toggle navigation" ]
, span [ class "icon-bar" ]
[]
, span [ class "icon-bar" ]
[]
, span [ class "icon-bar" ]
[]
]
, a [ class "navbar-brand page-scroll", href "#page-top", onClick (Update.ClickedElem "#page-top") ]
[ text "Start Bootstrap" ]
]
, div [ class "collapse navbar-collapse", id "bs-example-navbar-collapse-1" ]
[ ul [ class "nav navbar-nav navbar-right" ]
[ li [ class "hidden" ]
[ a [ href "#page-top" ]
[]
]
, li []
[ a [ class "page-scroll", href "#services", onClick (Update.ClickedElem "#services") ]
[ text "Services" ]
]
, li []
[ a [ class "page-scroll", href "#portfolio", onClick (Update.ClickedElem "#portfolio") ]
[ text "Portfolio" ]
]
, li []
[ a [ class "page-scroll", href "#about", onClick (Update.ClickedElem "#about") ]
[ text "About" ]
]
, li []
[ a [ class "page-scroll", href "#team", onClick (Update.ClickedElem "#team") ]
[ text "Team" ]
]
, li []
[ a [ class "page-scroll", href "#contact", onClick (Update.ClickedElem "#contact") ]
[ text "Contact" ]
]
]
]
, text " "
]
]
, section [ id "services" ]
[-- More content
]
-- Much more content
, section [ id "contact" ]
[-- More content
]
]
-- partial update
case msg of
ClickedElem elem ->
-- Sends the clicked element '#services' to the JS world
( model, clickedElem elem )
ChangeLocation newLocation ->
( { model
| location = newLocation
}
, Cmd.none
)
-- the port
port clickedElem : String -> Cmd msg
port changeLoc : (String -> msg) -> Sub msg
-- subscription
subscriptions model =
Sub.batch
[ Window.resizes Update.Resize
, Update.changeLoc Update.ChangeLocation
]
On the JS-side I then use the jQuery animate / scrollTo function, like so:
<script>
app.ports.clickedElem.subscribe(function(id){
console.log("Scrolling.");
// Why isn't this working properly?
$('html, body').animate({
scrollTop: $(id).offset().top
}, 2000);
// return false;
app.ports.changeLoc.send(id);
});
</script>
Finally the new location get's passed back to Elm through a port called changeLoc
which just updates my model.
What am I missing here? Is this a problem due to the virtual-dom of Elm? I can't seem to figure this out. The offsets of the provided anchors are all fine and the function get's called as it's supposed to.
Here's the way to fix this issue:
Instead of using onClick
I used the tip by @ChadGilbert and went for onWithOptions
where I set preventDefault
to True
.
The outcome:
onWithOptions "click" { stopPropagation = True, preventDefault = True } (Json.succeed (Update.ClickedElem "#anchor"))
instead of
onClick (Update.ClickedElem "#anchor")
Thanks!