Scroll.Task¶
This is everything Scroll.Task offers:
| Function | Type |
|---|---|
scroll |
(ScrollBuilder -> ScrollBuilder) -> Task ScrollError (List ScrollOk) |
scrollEach |
(ScrollBuilder -> ScrollBuilder) -> Task Never (List (Result ScrollError ScrollOk)) |
delay |
Int -> ScrollBuilder -> ScrollBuilder |
duration |
Int -> ScrollBuilder -> ScrollBuilder |
speed |
Float -> ScrollBuilder -> ScrollBuilder |
easing |
Easing -> ScrollBuilder -> ScrollBuilder |
Two triggers, plus timing and easing. Same fire-and-forget mental model as Cmd, but the result is typed - so you can:
- find out whether the scroll succeeded or failed,
- chain a scroll with other
Tasks (e.g. fetch data, then scroll to it), - decide what to do when one scroll in a sequence fails.
If you don't need any of those, Cmd is simpler. If you need pause / resume / mid-flight redirect / per-frame progress, you want Sub.
Example¶
View Example
View Source Code
module Scroll.Task.FirstScroll.Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class, id, style)
import Html.Events exposing (onClick)
import Motion.Easing as Easing exposing (Easing(..))
import Scroll.Builder as Scroll
import Scroll.Engine.Task as Task exposing (ScrollBuilder)
import Task
-- MAIN
main : Program () Model Msg
main =
Browser.element
{ init = \_ -> init
, view = view
, update = update
, subscriptions = always Sub.none
}
-- MODEL
type alias Model =
{ status : ScrollStatus }
type ScrollStatus
= Idle
| Scrolling
| Arrived
| Failed String
init : ( Model, Cmd Msg )
init =
( { status = Idle }, Cmd.none )
-- UPDATE
type Msg
= ScrollTo String
| ScrollResult (Result Task.ScrollError (List Task.ScrollOk))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollTo targetId ->
( { model | status = Scrolling }
, Task.attempt ScrollResult <|
Task.scroll <|
scrollToElement targetId
)
ScrollResult (Ok _) ->
( { model | status = Arrived }, Cmd.none )
ScrollResult (Err (Task.ScrollError err)) ->
let
containerLabel =
case err.container of
Task.Document ->
"document"
Task.Container id ->
id
in
( { model | status = Failed ("Scroll failed for container: " ++ containerLabel) }
, Cmd.none
)
scrollToElement : String -> ScrollBuilder -> ScrollBuilder
scrollToElement targetId =
Scroll.forContainer "scroll-container"
>> Scroll.toElement targetId
>> Scroll.speed 250
>> Scroll.easing BounceOut
>> Scroll.build
-- VIEW
view : Model -> Html Msg
view model =
div [ class "example-stage" ]
[ div [ class "example-controls" ]
[ styledButton (ScrollTo "top-element") "Scroll to Top"
, styledButton (ScrollTo "middle-element") "Scroll to Middle"
, styledButton (ScrollTo "bottom-element") "Scroll to Bottom"
]
, statusBar model.status
div
[ id "scroll-container"
, style "width" "100%"
, style "flex" "1 1 auto"
, style "min-height" "0"
, style "box-sizing" "border-box"
, style "overflow-y" "auto"
, style "border" "2px solid #333"
, style "border-radius" "8px"
]
[ scrollContent ]
]
statusBar : ScrollStatus -> Html msg
statusBar status =
let
( color, message ) =
case status of
Idle ->
( "#94a3b8", "Click a button to scroll" )
Scrolling ->
( "#f59e0b", "Scrolling..." )
Arrived ->
( "#22c55e", "✓ Scroll complete" )
Failed err ->
( "#ef4444", "✗ " ++ err )
in
div
[ style "padding" "6px 14px"
, style "border-radius" "6px"
, style "background-color" color
, style "color" "white"
, style "font-size" "clamp(11px, 1.8vmin, 14px)"
, style "font-weight" "500"
, style "flex" "0 0 auto"
]
[ text message ]
styledButton : Msg -> String -> Html Msg
styledButton msg label =
button
[ onClick msg
, class "ui-action-button"
, style "background-color" "#6366f1"
]
[ text label ]
scrollContent : Html Msg
scrollContent =
div
[ style "padding" "20px" ]
[ targetElement "top-element" "Top Section" "#4CAF50"
, spacer
, targetElement "middle-element" "Middle Section" "#2196F3"
, spacer
, targetElement "bottom-element" "Bottom Section" "#9C27B0"
]
targetElement : String -> String -> String -> Html Msg
targetElement elementId label color =
div
[ id elementId
, style "padding" "40px"
, style "background-color" color
, style "color" "white"
, style "border-radius" "8px"
, style "text-align" "center"
, style "font-size" "24px"
]
[ text label ]
spacer : Html Msg
spacer =
div [ style "height" "400px" ] []
Trigger¶
Task.scroll returns a Task ScrollError (List ScrollOk). Turn it into a Cmd with Task.attempt:
View Source Code
import Scroll.Engine.Task as Task
import Task as TaskCore
type Msg
= ScrollTo String
| GotScrollResult (Result Task.ScrollError (List Task.ScrollOk))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollTo targetId ->
( model
, scrollToSection targetId
|> Task.scroll
|> TaskCore.attempt GotScrollResult
)
GotScrollResult (Ok _) ->
( { model | status = "Arrived" }, Cmd.none )
GotScrollResult (Err _) ->
( { model | status = "Scroll failed" }, Cmd.none )
Handling Results¶
ScrollOk¶
ScrollOk is a type alias for { container : Container, targetElementId : Maybe String }.
It is returned for every scroll that completed successfully, and tells you which container finished and, if you targeted a specific element, which one.
| Field | Type | Description |
|---|---|---|
container |
Container |
The container that was scrolled (Document or Container "id") |
targetElementId |
Maybe String |
The element ID, if toElement was used |
View Source Code
ScrollError¶
Returned when a scroll fails. Tells you what was being scrolled, which target was involved, and the underlying DOM error - usually because the container or target element wasn't in the DOM.
| Field | Type | Description |
|---|---|---|
container |
Container |
The container that was being scrolled |
targetElementId |
Maybe String |
The element ID, if one was specified |
domError |
Dom.Error |
The underlying browser DOM error |
View Source Code
scroll vs scrollEach¶
Task.scroll is fail-fast: the first scroll to fail ends the task immediately, and later scrolls in the same builder are skipped. You only get Ok if every scroll completed - and at that point the payload lists them all, in order. The moment one fails you get Err with no record of the ones that succeeded before it.
If you'd rather get a result for every target - failures included - use Task.scrollEach:
View Source Code
scrollEach always completes and returns one Result per target.
Task Composition¶
Because Task.scroll is a regular Task, you can compose it with anything else that returns a Task. Example - fetch data, then scroll to wherever the response points:
View Source Code
Caveats¶
Task suffers the two pre-calculation trade-offs that Cmd does:
- Timing drift on busy pages or high-refresh-rate displays.
- Re-triggering doesn't cancel - parallel scrolls compete for the container, longest wins.
Both are fixed by Sub if they matter to you.
Next Steps¶
Need state, mid-flight redirects, pause / resume, or per-frame progress?