dj-viewport — bidirectional infinite scroll

Scroll up → older messages load. Scroll down → newer messages load. Two viewport sentinels handle the IntersectionObserver wiring; your view just supplies load_older and load_newer handlers.

Message stream

50 loaded (1000–1049)

Scroll to the very top or the very bottom of the box below to trigger a load.

eveMessage #1000 — lorem ipsum dolor sit amet, consectetur.16:40
frankMessage #1001 — lorem ipsum dolor sit amet, consectetur.17:47
aliceMessage #1002 — lorem ipsum dolor sit amet, consectetur.18:54
bobMessage #1003 — lorem ipsum dolor sit amet, consectetur.19:01
carolMessage #1004 — lorem ipsum dolor sit amet, consectetur.20:08
daveMessage #1005 — lorem ipsum dolor sit amet, consectetur.21:15
eveMessage #1006 — lorem ipsum dolor sit amet, consectetur.22:22
frankMessage #1007 — lorem ipsum dolor sit amet, consectetur.23:29
aliceMessage #1008 — lorem ipsum dolor sit amet, consectetur.00:36
bobMessage #1009 — lorem ipsum dolor sit amet, consectetur.01:43
carolMessage #1010 — lorem ipsum dolor sit amet, consectetur.02:50
daveMessage #1011 — lorem ipsum dolor sit amet, consectetur.03:57
eveMessage #1012 — lorem ipsum dolor sit amet, consectetur.04:04
frankMessage #1013 — lorem ipsum dolor sit amet, consectetur.05:11
aliceMessage #1014 — lorem ipsum dolor sit amet, consectetur.06:18
bobMessage #1015 — lorem ipsum dolor sit amet, consectetur.07:25
carolMessage #1016 — lorem ipsum dolor sit amet, consectetur.08:32
daveMessage #1017 — lorem ipsum dolor sit amet, consectetur.09:39
eveMessage #1018 — lorem ipsum dolor sit amet, consectetur.10:46
frankMessage #1019 — lorem ipsum dolor sit amet, consectetur.11:53
aliceMessage #1020 — lorem ipsum dolor sit amet, consectetur.12:00
bobMessage #1021 — lorem ipsum dolor sit amet, consectetur.13:07
carolMessage #1022 — lorem ipsum dolor sit amet, consectetur.14:14
daveMessage #1023 — lorem ipsum dolor sit amet, consectetur.15:21
eveMessage #1024 — lorem ipsum dolor sit amet, consectetur.16:28
frankMessage #1025 — lorem ipsum dolor sit amet, consectetur.17:35
aliceMessage #1026 — lorem ipsum dolor sit amet, consectetur.18:42
bobMessage #1027 — lorem ipsum dolor sit amet, consectetur.19:49
carolMessage #1028 — lorem ipsum dolor sit amet, consectetur.20:56
daveMessage #1029 — lorem ipsum dolor sit amet, consectetur.21:03
eveMessage #1030 — lorem ipsum dolor sit amet, consectetur.22:10
frankMessage #1031 — lorem ipsum dolor sit amet, consectetur.23:17
aliceMessage #1032 — lorem ipsum dolor sit amet, consectetur.00:24
bobMessage #1033 — lorem ipsum dolor sit amet, consectetur.01:31
carolMessage #1034 — lorem ipsum dolor sit amet, consectetur.02:38
daveMessage #1035 — lorem ipsum dolor sit amet, consectetur.03:45
eveMessage #1036 — lorem ipsum dolor sit amet, consectetur.04:52
frankMessage #1037 — lorem ipsum dolor sit amet, consectetur.05:59
aliceMessage #1038 — lorem ipsum dolor sit amet, consectetur.06:06
bobMessage #1039 — lorem ipsum dolor sit amet, consectetur.07:13
carolMessage #1040 — lorem ipsum dolor sit amet, consectetur.08:20
daveMessage #1041 — lorem ipsum dolor sit amet, consectetur.09:27
eveMessage #1042 — lorem ipsum dolor sit amet, consectetur.10:34
frankMessage #1043 — lorem ipsum dolor sit amet, consectetur.11:41
aliceMessage #1044 — lorem ipsum dolor sit amet, consectetur.12:48
bobMessage #1045 — lorem ipsum dolor sit amet, consectetur.13:55
carolMessage #1046 — lorem ipsum dolor sit amet, consectetur.14:02
daveMessage #1047 — lorem ipsum dolor sit amet, consectetur.15:09
eveMessage #1048 — lorem ipsum dolor sit amet, consectetur.16:16
frankMessage #1049 — lorem ipsum dolor sit amet, consectetur.17:23

Pairs nicely with dj-virtual for streams that may grow into the tens of thousands — viewport sentinels handle infinite-load, virtualization keeps the DOM small.

The whole pattern

<!-- dj-stream pairs with the two viewport sentinels -->
<div dj-stream="messages"
     dj-viewport-top="load_older"
     dj-viewport-bottom="load_newer">
    {% for m in messages %}
        <div class="msg">{{ m.author }}: {{ m.text }}</div>
    {% endfor %}
</div>

The framework wires IntersectionObservers automatically. dj-viewport-top fires when the top of the stream becomes visible (you've scrolled to the top); dj-viewport-bottom fires for the bottom. Each fires at most once per scroll-into-view to avoid loops.