Last active
May 6, 2024 16:15
-
-
Save nberger/b5e316a43ffc3b7d5e084b228bd83899 to your computer and use it in GitHub Desktop.
Reagent infinite scroll
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns views.infinite-scroll | |
(:require | |
[reagent.core :as r])) | |
(defn- get-scroll-top [] | |
(if (exists? (.-pageYOffset js/window)) | |
(.-pageYOffset js/window) | |
(.-scrollTop (or (.-documentElement js/document) | |
(.-parentNode (.-body js/document)) | |
(.-body js/document))))) | |
(defn- get-el-top-position [node] | |
(if (not node) | |
0 | |
(+ (.-offsetTop node) (get-el-top-position (.-offsetParent node))))) | |
(defn- safe-component-mounted? [component] | |
(try (boolean (r/dom-node component)) (catch js/Object _ false))) | |
(defn debounce | |
"Returns a function that will call f only after threshold has passed without new calls | |
to the function. Calls prep-fn on the args in a sync way, which can be used for things like | |
calling .persist on the event object to be able to access the event attributes in f" | |
([threshold f] (debounce threshold f (constantly nil))) | |
([threshold f prep-fn] | |
(let [t (atom nil)] | |
(fn [& args] | |
(when @t (js/clearTimeout @t)) | |
(apply prep-fn args) | |
(reset! t (js/setTimeout #(do | |
(reset! t nil) | |
(apply f args)) | |
threshold)))))) | |
(defn infinite-scroll [props] | |
;; props is a map with :can-show-more? & :load-fn keys | |
(let [listener-fn (atom nil) | |
detach-scroll-listener (fn [] | |
(when @listener-fn | |
(.removeEventListener js/window "scroll" @listener-fn) | |
(.removeEventListener js/window "resize" @listener-fn) | |
(reset! listener-fn nil))) | |
should-load-more? (fn [this] | |
(let [node (r/dom-node this) | |
scroll-top (get-scroll-top) | |
my-top (get-el-top-position node) | |
threshold 50] | |
(< (- (+ my-top (.-offsetHeight node)) | |
scroll-top | |
(.-innerHeight js/window)) | |
threshold))) | |
scroll-listener (fn [this] | |
(when (safe-component-mounted? this) | |
(let [{:keys [load-fn can-show-more?]} (r/props this)] | |
(when (and can-show-more? | |
(should-load-more? this)) | |
(println "loading more...") | |
(detach-scroll-listener) | |
(load-fn))))) | |
debounced-scroll-listener (debounce 200 scroll-listener) | |
attach-scroll-listener (fn [this] | |
(let [{:keys [can-show-more?]} (r/props this)] | |
(when can-show-more? | |
(when-not @listener-fn | |
(reset! listener-fn (partial debounced-scroll-listener this)) | |
(.addEventListener js/window "scroll" @listener-fn) | |
(.addEventListener js/window "resize" @listener-fn)))))] | |
(r/create-class | |
{:component-did-mount | |
(fn [this] | |
(attach-scroll-listener this)) | |
:component-did-update | |
(fn [this _] | |
(attach-scroll-listener this)) | |
:component-will-unmount | |
detach-scroll-listener | |
:reagent-render | |
(fn [props] | |
[:div])}))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment