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.
@action
dj-form-pending
@server_function
dj-transition
ActivityMixin
WizardMixin
dj_suspense
defer()
DataTableMixin
dj-virtual
dj-viewport
UploadMixin
Message stream
50 loaded (1000–1049)Scroll to the very top or the very bottom of the box below to trigger a load.
Message #1000 — lorem ipsum dolor sit amet, consectetur.16:40
Message #1001 — lorem ipsum dolor sit amet, consectetur.17:47
Message #1002 — lorem ipsum dolor sit amet, consectetur.18:54
Message #1003 — lorem ipsum dolor sit amet, consectetur.19:01
Message #1004 — lorem ipsum dolor sit amet, consectetur.20:08
Message #1005 — lorem ipsum dolor sit amet, consectetur.21:15
Message #1006 — lorem ipsum dolor sit amet, consectetur.22:22
Message #1007 — lorem ipsum dolor sit amet, consectetur.23:29
Message #1008 — lorem ipsum dolor sit amet, consectetur.00:36
Message #1009 — lorem ipsum dolor sit amet, consectetur.01:43
Message #1010 — lorem ipsum dolor sit amet, consectetur.02:50
Message #1011 — lorem ipsum dolor sit amet, consectetur.03:57
Message #1012 — lorem ipsum dolor sit amet, consectetur.04:04
Message #1013 — lorem ipsum dolor sit amet, consectetur.05:11
Message #1014 — lorem ipsum dolor sit amet, consectetur.06:18
Message #1015 — lorem ipsum dolor sit amet, consectetur.07:25
Message #1016 — lorem ipsum dolor sit amet, consectetur.08:32
Message #1017 — lorem ipsum dolor sit amet, consectetur.09:39
Message #1018 — lorem ipsum dolor sit amet, consectetur.10:46
Message #1019 — lorem ipsum dolor sit amet, consectetur.11:53
Message #1020 — lorem ipsum dolor sit amet, consectetur.12:00
Message #1021 — lorem ipsum dolor sit amet, consectetur.13:07
Message #1022 — lorem ipsum dolor sit amet, consectetur.14:14
Message #1023 — lorem ipsum dolor sit amet, consectetur.15:21
Message #1024 — lorem ipsum dolor sit amet, consectetur.16:28
Message #1025 — lorem ipsum dolor sit amet, consectetur.17:35
Message #1026 — lorem ipsum dolor sit amet, consectetur.18:42
Message #1027 — lorem ipsum dolor sit amet, consectetur.19:49
Message #1028 — lorem ipsum dolor sit amet, consectetur.20:56
Message #1029 — lorem ipsum dolor sit amet, consectetur.21:03
Message #1030 — lorem ipsum dolor sit amet, consectetur.22:10
Message #1031 — lorem ipsum dolor sit amet, consectetur.23:17
Message #1032 — lorem ipsum dolor sit amet, consectetur.00:24
Message #1033 — lorem ipsum dolor sit amet, consectetur.01:31
Message #1034 — lorem ipsum dolor sit amet, consectetur.02:38
Message #1035 — lorem ipsum dolor sit amet, consectetur.03:45
Message #1036 — lorem ipsum dolor sit amet, consectetur.04:52
Message #1037 — lorem ipsum dolor sit amet, consectetur.05:59
Message #1038 — lorem ipsum dolor sit amet, consectetur.06:06
Message #1039 — lorem ipsum dolor sit amet, consectetur.07:13
Message #1040 — lorem ipsum dolor sit amet, consectetur.08:20
Message #1041 — lorem ipsum dolor sit amet, consectetur.09:27
Message #1042 — lorem ipsum dolor sit amet, consectetur.10:34
Message #1043 — lorem ipsum dolor sit amet, consectetur.11:41
Message #1044 — lorem ipsum dolor sit amet, consectetur.12:48
Message #1045 — lorem ipsum dolor sit amet, consectetur.13:55
Message #1046 — lorem ipsum dolor sit amet, consectetur.14:02
Message #1047 — lorem ipsum dolor sit amet, consectetur.15:09
Message #1048 — lorem ipsum dolor sit amet, consectetur.16:16
Message #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.