dominikmayer/elm-virtual-list/1.0.0

NOTE: the latest version is 2.1.0

VirtualList

Efficiently displays large lists by only rendering the visible items within the viewport, plus a configurable buffer.

It does so by dynamically measuring the height of the displayed elements.

In case you know the heights in advance you might get a better performance by using FabienHenon/elm-infinite-list-view.

Usage

Model Setup

Include VirtualList.Model in your app's model:

import VirtualList


type alias Model =
    { virtualList : VirtualList.Model
    -- other fields
    }


defaultModel : Model
defaultModel =
    { virtualList = VirtualList.init
    -- other fields
    }

Update Function

Include VirtualList.Msg in your app’s update function:

type Msg
    = VirtualListMsg VirtualList.Msg
    -- other messages


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        VirtualListMsg virtualListMsg ->
            let
                ( virtualListModel, virtualListCmd ) =
                    VirtualList.update virtualListMsg model.virtualList
            in
                ( { model | virtualList = virtualListModel }, Cmd.map VirtualListMsg virtualListCmd )
        -- other cases

Rendering

Render the virtual list in your view:

view : Model -> Html Msg
view model =
    VirtualList.view (renderRow model) model.virtualList VirtualListMsg


renderRow : Model -> String -> Html Msg
renderRow model id =
    div [] [text id]

Model & Initialization

type alias Model =
{ listId : String
, ids : List String
, height : Float
, defaultItemHeight : Float
, baseBuffer : Int
, dynamicBuffer : Bool
, buffer : Int
, showList : Bool
, visibleRange : ( Int, Int )
, firstRender : Bool
, unmeasuredRows : Set Int
, rowHeights : Dict Int RowHeight
, cumulativeHeights : Dict Int Float
, scrollTop : Float
, previousScrollTop : Float
}

Model for maintaining the virtual list state. You need to include it in your model:

type alias Model =
    { virtualList : VirtualList.Model
    -- other fields
    }

You create one with the init function.

defaultConfig : Config

Provides a default configuration with sensible initial values.

  • listId: The ID of the list container in the DOM.

  • initialHeight: Estimated height of the list before it is measured.

  • defaultItemHeight: Default height assigned to items before they are measured.

  • showListDuringInitialMeasure: If True, the list is visible while measuring (potentially causing spacing issues).

  • buffer: Number of items rendered outside the visible range to ensure smooth scrolling.

  • dynamicBuffer: If True, increases buffer size when scrolling quickly.

    defaultConfig : Config defaultConfig = { listId = "virtual-list" , initialHeight = 500 , defaultItemHeight = 26 , showListDuringInitialMeasure = False , buffer = 5 , dynamicBuffer = True }

init : Model

Initializes a virtual list model using the default configuration.

initWithConfig : Config -> Model

Initializes a virtual list model using a custom configuration.

initWithConfig defaultConfig

You can modify the default configuration:

config =
    { defaultConfig | buffer = 10 }
type Msg

Messages handled by the virtual list.

You need to include VirtualList.Msg in your app’s Msg type and handle it in update.

type Msg
   = VirtualListMsg VirtualList.Msg
   -- other messages
update : Msg -> Model -> ( Model, Cmd Msg )

Updates the virtual list model in response to messages.

You must integrate this into your update function and map the result back to your own Msg.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        VirtualListMsg virtualListMsg ->
            let
                ( virtualListModel, virtualListCmd ) =
                    VirtualList.update virtualListMsg model.virtualList
            in
                ( { model | virtualList = virtualListModel }, Cmd.map VirtualListMsg virtualListCmd )
        -- other cases

Rendering

view : (String -> Html msg) -> Model -> (Msg -> msg) -> Html msg

Renders the virtual list.

You provide it with

  • function that renders an item given its ID,
  • the virtual list Model and
  • the virtual list message type on your side.

In your code this would look like this:

view : Model -> Html Msg
view model =
    VirtualList.view (renderRow model) model.virtualList VirtualListMsg


renderRow : Model -> String -> Html Msg
renderRow model id =
    div [] [text id]

renderRow is executed lazily.

Updating the Items

setItems : Model -> List String -> ( Model, Cmd Msg )

Updates the list of items displayed in the virtual list.

Only new items are considered for measurement; existing items retain their cached heights.

VirtualList.setItems model.virtualList ids
setItemsAndRemeasureAll : Model -> List String -> ( Model, Cmd Msg )

Same as setItems, but allows remeasuring the entire list.

Use this when item heights may have changed.

VirtualList.setItemsAndRemeasureAll model.virtualList newIds
Model
-> { newIds : List String
, idsToRemeasure : List String
}
-> ( Model, Cmd Msg )

Same as setItems, but allows specifying which items should be remeasured.

This is useful when only a subset of items might have changed in height.

VirtualList.setItemsAndRemeasure model.virtualList { newIds = newIds, idsToRemeasure = changedIds }

Scrolling

scrollToItem : Model -> String -> Alignment -> Cmd Msg

Scrolls to the specified item in the virtual list.

Does nothing if the item is already visible.

Cmd.map VirtualListMsg (VirtualList.scrollToItem model.virtualList "item-42" VirtualList.Center)
= Top
| Center
| Bottom

Defines where an item should appear in the viewport when scrolled to.

Variants:

  • Top: Scrolls the item to the top.
  • Center: Scrolls the item to the center.
  • Bottom: Scrolls the item to the bottom.