디자인 패턴으로서의 무한 스크롤

6866 단어
무한 스크롤을 구현하려는 소프트웨어 개발자는 브라우저IntersectionObserver 콜백 기능으로 쉽게 시작할 수 있습니다.

실제 예제로 독자가 원본 기사의 끝에 도달하면 다른 기사를 자동으로 가져오는 블로그 사이트에서 사용할 구성 요소를 만들 수 있습니다.

코드는 다음과 같습니다.

const observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting == true) {
        fetchNextArticle();
    }
}, {threshold: 0});

const bottomOfPage = document.querySelect('#bottom-of-page');
observer.observe(bottomOfPage);


별로 없습니다. bottom-of-page 요소는 모든 HTML 요소가 될 수 있지만 가장 단순한 형태에서는 원본 기사의 내용 뒤 어딘가에 있는 0이 아닌 차원을 가진 clear div입니다.

관찰자는 독자가 대상div이 보이는 지점으로 스크롤할 때 호출됩니다. threshold0 값은 div의 일부가 뷰포트에 있을 때 콜백을 트리거합니다. threshold1 값은 div 모두가 뷰포트에 있을 때만 트리거합니다.
fetchNextArticle 함수는 Just-In-Time 로드가 발생하는 곳입니다.

const articleUrls = [
    '/feb-28-2021.html',
    '/feb-21-2021.html',
    '/feb-14-2021.html',
    '/feb-07-2021.html'
];

const nextIndex = 0;

async fetchNextArticle() {  
    // fetch the article using HTTP
    const response = await fetch(articleUrls[nextIndex++]);
    if (response.status != 200 && response.status != 304)
        return;
    const templateText = await response.text();

    // get the <body> of the article's HTML
    const template = document.createElement('template');
    template.innerHTML = templateText;
    const body = template.content.querySelector('body');

    // create a new <article> and insert it
    const nextArticle = document.createElement('article')
    nextArticle.innerHTML = body.innerHTML; 
    bottomOfPage.parentNode.insertBefore(nextArticle, bottomOfPage);
}


이 예에는 Just-In-Time 로드를 위해 대기 중인 4개의 기사가 있습니다. 물론 실제로 그 대기열은 독자의 관심사에 맞게 동적으로 구축되어야 합니다.

이 기본적인 예는 좋은 출발점이지만 숙련된 개발자는 개선 사항을 쉽게 상상할 수 있습니다.
  • 다음 기사의 및 <meta> 태그를 쿼리하여 추천 이미지, 날짜 표시줄, 기준선 및 단락 리드와 같은 열린 그래프 데이터를 추출합니다. </li><li> 기사의 <본문>에서 불필요한 머리글, 바닥글, 메뉴 및 광고 링크를 제거합니다. </li><li> 독자에게 기사의 두 가지 보기인 축소된 UI 카드와 확장된 전체 텍스트 보기 간에 전환할 수 있는 방법을 제공합니다. </li><br>이 모든 것이 <a href="https://domcomponents.com/components/piqueme.blue?utm_term=InfiniteScrollAsADesignPattern" rel="noreferrer noopener nofollow">rwt-piqueme</a> DOM 구성 요소에서 구현되었으며 <a href="https://www.npmjs.com/package/rwt-piqueme" rel="noreferrer noopener nofollow">NPM</a> 을 통해 바로 사용할 수 있습니다.<br><br><br>(이 기사의 확장 버전은 원래 <a href="https://medium.com/javascript-fanboi/2021-040-infinite-scroll-as-a-design-pattern-859ecc45287a" rel="noreferrer noopener nofollow">Medium</a> 에 게재되었습니다.) <div class=""> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1344493394064923" crossorigin="anonymous"></script> <!-- geeks-中间底部-01 --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-1344493394064923" data-ad-slot="9316917350" data-ad-format="auto" data-full-width-responsive="true"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="source-div mt-6 "> <div class=""> <div class="alert alert-light col-12 " role="alert" style="background-color:#f2f2f2; line-height: 2.2;"> <h5 class="text-dark">Reference</h5> 이 문제에 관하여(디자인 패턴으로서의 무한 스크롤), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다<a href="https://dev.to/joehonton/infinite-scroll-as-a-design-pattern-379" rel="noreferrer noopener nofollow" class=" h5 text-warning mb-3"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-link-45deg" viewBox="0 0 16 16"> <path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"></path> <path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"></path> </svg> https://dev.to/joehonton/infinite-scroll-as-a-design-pattern-379</a> <hr class="my-2"> <p class="m-0 p-0 small " style="font-size: 12px;line-height: 1.5;"> 텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.</p> <p class="mt-1 mb-0 p-0"> <img alt="img" class=" p-0 m-0" src="/static/v3_theme_01_asset/img/intrepidgeeks_logo.svg" id="navbar-logo-b" style="max-width:60px;"> <span class="m-0 p-0 small" style="line-height: 1.5;">우수한 개발자 콘텐츠 발견에 전념</span> (<small class="text-muted">Collection and Share based on the CC Protocol.</small>) </p> </div> </div> </div> </div> <div class="row mb-4"> <div class="col-6 text-primary text-capitalize "> <a class=" btn bg-primary-soft small float-left text-capitalize " href="/tutorial/codeforces-97b-superset"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"> <line x1="19" y1="12" x2="5" y2="12"></line> <polyline points="12 19 5 12 12 5"></polyline> </svg> CodeForces 97B Superset</a> </div> <div class="col-6 text-primary text-capitalize "> <a class=" btn bg-primary-soft small float-right text-capitalize " href="/tutorial/the-use-of-break-is-particularly-confusing">break 의 용법 은 특히 헷 갈 리 기 쉽다. <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div></div> <div class="row mb-4"> <div class="col-xl-12 col-12"> <div class="container-fluid"> <h4 class="card-title ">좋은 웹페이지 즐겨찾기</h4> <!--begin::Stats Widget 4--> <div class="card card-custom card-stretch gutter-b ai-recommended-bg-03 shadow-sm"> <!--begin::Body--> <div class="card-body d-flex align-items-center py-0 mt-2 "> <div class="d-flex flex-column flex-grow-1 py-2 py-lg-5"> <p class="mb-2 text-muted">개발자 우수 사이트 수집</p> <a href="/" target="_blank" class="card-title font-weight-bolder text-primary font-size-h3 mb-2 text-hover-primary "> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-link-45deg" viewBox="0 0 16 16"> <path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"></path> <path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"></path> </svg> 개발자가 알아야 할 필수 사이트 100선 추천</a> <span class="font-weight-bold text-muted font-size-lg">우리는 당신을 위해 100개의 자주 사용하는 개발자 학습 사이트를 정리했습니다</span> </div> <img src="/static/v3_theme_01_asset/img/website-nav-pages-img3.svg" alt="best100-homepage" class="align-self-end h-20px img-responsive border-small " style="width:36%;"> </div> <!--end::Body--> </div> <!--end::Stats Widget 4--> </div> </div> </div> <div class="row mb-4"> <aside class="col-md-12 col-xxl-12 d-none d-md-block float-right pt-3 m-2 "> <div class="row"><h4 class="card-title ">관련 게시물</h4><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/vue3-challenge-form-editor"> <h4 class="fw-bold"> Vue3 챌린지: 양식 편집기 </h4> <p class="text-muted">개발자로서 스키마에 정의된 양식을 생성하고 개체를 편집할 수 있는 구성 요소XForm가 필요합니다. v-model에서 개체를 허용합니다 fields 속성 에서 개체 배열을 허용합니다. <form> 배열 의 길이와 같은 양의 자식 요소로 렌더링fields 필드의 각field은 하위 에 대한 구성을 설명합니다. field.component는 렌더링해야 하는 구성 요소(또는 태그)의 이름을 가져옵...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/mongodb-no-banco-nosql-is-no-longer-available"> <h4 class="fw-bold"> MongoDB - No Banco NoSQL은 더 이상 사용할 수 없습니다. </h4> <p class="text-muted">De la pra cá, este banco de dados tem crescido em utilização e em popularidade. 그렇다면 SQL Server, Oracle 또는 MySQL과 다른 점은 무엇입니까? O MongoDB é um banco de dado que não armazena os dados em um formato tabelar e relacional. Os...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/how-to-use-relative-selectors-in-shirates-part-2"> <h4 class="fw-bold"> Shirates에서 상대 선택기를 사용하는 방법 - 2부 - </h4> <p class="text-muted">이것은 간단하고 강력한 표현입니다(참조). 위젯 흐름과 함께 상대적으로 위젯을 얻을 수 있습니다. 위젯 흐름 알고리즘 위젯 흐름 알고리즘은 수직 위치(첫 번째 그룹 - 여섯 번째 그룹)로 위젯을 그룹화한 다음 각 그룹의 위젯을 왼쪽에서 오른쪽으로, 그리고 위에서 아래로 검색합니다. 첫 번째 그룹에서 위젯 흐름 순서는 (0),(1),(2)입니다. 두 번째 그룹에서 위젯 흐름 순서는 (3),(4...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/stop-saving-lines"> <h4 class="fw-bold"> 라인 저장을 중지하십시오! </h4> <p class="text-muted">이 기사에서는 소프트웨어를 개발할 때 더 많은 코드를 작성해야 하는 이유에 대해 논할 것입니다. 책임별로 코드를 나누면 명확성이 향상되고 프로젝트 유지 관리가 더 쉬워집니다. 그건 그렇고, 여기서 목표는 코드를 더 길게 하기 위해 줄을 추가하는 것이 아니라 가독성을 우선시하는 것입니다. 많은 언어, 특히 JavaScript는 멋진 기능을 많이 제공하므로 더 적거나 더 명확한 코드로 동일한 알...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/rpm-system-system-nfs-sunucusu-oluturma"> <h4 class="fw-bold"> RPM 시스템 시스템 NFS Sunucusu Oluşturma </h4> <p class="text-muted">NFS는 Network File System, dosyalarımızı ağ üzerinden paylaşmamıza olanak sağlayan bir sistemdir. NFS kullanarak sunucu olan bilgisayarda paylaşılan dizinleri client olan bilgisayarlara mount edebiliriz. Mount işlemini ge...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/azure-ad-client-password-expiration"> <h4 class="fw-bold"> Azure AD 클라이언트 암호 만료 </h4> <p class="text-muted">Azure AD에서 새 클라이언트 암호를 생성하고 이를 사용하도록 웹 사이트 구성을 업데이트합니다. 문제는 이 문제가 비밀에 설정한 만료 날짜에 따라 3, 6, 12, 18 또는 24개월 후에 다시 발생한다는 것입니다. Twitter의 제안은 Logic Apps 및 Event Grid가 이 문제를 해결하는 데 도움이 될 수 있다는 것입니다. 그러나 먼저 다음 30일 내에 만료될 모든 비밀을 ...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/do-you-need-autoscaler-de-drone-ci-integration"> <h4 class="fw-bold"> ¿Autoscaler de Drone CI 통합이 필요합니까? </h4> <p class="text-muted">En este pequeño 블로그 vamos a tocar un tema muy Importante, instalar el drone CI y su autoscaler, una de las razones por las cuales me vi en la necesidad de integrarlo es debido a que nosotros usamos el drone-sever en una ...</p> </a> </div> </div> </div><div class="col-6 mb-2 pb-3"> <div class=" card card-border border-secondary shadow-sm border mb-6 mb-md-8 mb-lg-0 mt-2 mb-4 pb-3" style="border-color: #f2f2f2!important;"> <div class="card-body text-center"> <!-- Heading --> <a data-scroll class="nav-link text-primary small" href="/tutorial/responsive-card-using-flex-and-grid"> <h4 class="fw-bold"> 플렉스와 그리드를 이용한 반응형 카드 </h4> <p class="text-muted">몸을 풀다 그리드...</p> </a> </div> </div> </div></div></aside> </div> </article> <div class="col-0 col-xs-0 col-sm-0 col-md-3 col-lg-3 col-xl-3 col-xxl-3 m-0 p-0 "> <div class="navbar-expand-lg navbar-expand-lg sidebar sidebar-sticky " style="border-right: 1px solid rgba(0, 0, 0, .05);"> <!-- Navbar Collapse --> <div id="navbarVerticalNavMenu2 " class=" collapse navbar-collapse pt-3 card m-0 p-0 d-flex flex-row" style="margin: 0px!important;border-bottom: 0px solid rgba(0, 0, 0, .05);height: 100%;"> <div class=" h-100 pt-0 " style="margin: 0px!important;border-bottom: 0px solid rgba(0, 0, 0, .05);height: 100%; max-width: 260px;"> <div class="row bg-light-off mb-2 ai-recommended-bg-02-off " style="border-radius: 2px;"> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1344493394064923" crossorigin="anonymous"></script> <!-- geeks-右侧广告01-大横幅 --> <ins class="adsbygoogle" style="display:inline-block;width:300px;height:600px" data-ad-client="ca-pub-1344493394064923" data-ad-slot="7275371082"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="row bg-light-off mb-2 ai-recommended-bg-02-off " style="border-radius: 2px;"> </div> </div> </div> <!-- End Navbar Collapse --> </div> </div> </div> </div><!-- footer --> <footer class="bg-primary-soft p-3 mt-5"> <div class="container"> <div class="row gutter-3"> <div class="col-12 col-md-12 text-white text-center text-lowercase"> <p class="mb-0 text-muted small text-uppercase">© 2022 intrepidgeeks.com </p> <a href="/privacy" target="_blank" class="text-muted" rel="nofollow">Privacy Policy</a> <a href="/contact_us" target="_blank" class="text-muted" rel="nofollow">Contact US</a> <a href="/sitemap.xml" class="text-muted" target="_blank" rel="nofollow">Sitemap</a> </div> </div> </div> </footer> <!-- / footer --> <!-- Bootstrap 4 Version --> <div class="nk-cookie-banner alert alert-secondary text-center mb-0 small" role="alert" style="background: rgba(255, 255, 255, .6);border-color:#f2f2f2;-webkit-backdrop-filter: blur(10px);backdrop-filter: blur(20px);"> 🍪 This website uses cookies to ensure you get the best experience on our website. <a href="/privacy" target="blank" rel="nofollow">Learn more</a> <button type="button" class="btn btn-primary btn-sm ml-3" onclick="window.nk_hideCookieBanner()"> I Got It </button> </div><!-- JS Global Compulsory hs-builder:build-delete --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.8/dist/clipboard.min.js"></script> <script src="/static/v3_theme_01_asset/js/quick-website.min.js"></script> <script src="/static/all_theme_package/cookie-banner.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/darkmode-js@1.5.7/lib/darkmode-js.min.js"></script> <script> function addDarkmodeWidget() { const options = { bottom: '64px', // default: '32px' right: '36px', // default: '32px' left: 'unset', // default: 'unset' time: '0.4s', // default: '0.3s' mixColor: '#fff', // default: '#fff' backgroundColor: '#fff', // default: '#fff' buttonColorDark: '#666666', // default: '#100f2c' buttonColorLight: '#fff', // default: '#fff' saveInCookies: true, // default: true, label: '🌙', // default: '' autoMatchOsTheme: false // default: true } new Darkmode(options).showWidget(); } window.addEventListener('load', addDarkmodeWidget); </script> <script async > var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?62e51dcb98a631eb7af0fd338250ca43"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> <script src="/static/all_theme_package/auto-enable-prism.js"></script> <script src="/static/all_theme_package/prism.min.js"></script> <script> $(document).ready(function () { $('#post-content h1').each(function (i, el) { var c = $(el).attr("class"); $(el).replaceWith('<h2 class="' + c + '">' + $(el).html() + '</h2>') }); }); h2_length = $("#post-content h2").length // console.log(h2_length); h3_length = $("#post-content h3").length // console.log(h3_length); $(document).ready(function () { if (h2_length < 1 && h3_length > 0) { $('#post-content h3').each(function (i, el) { var c = $(el).attr("class"); $(el).replaceWith('<h2 class="' + c + '">' + $(el).html() + '</h2>') }); } ; }); </script> <script src="/static/all_theme_package/thirdparty/blogmenu/jquery.autoMenu.js"></script> <script> $(document).ready(function () { $("#blog-autoMenu").autoMenu({ levelOne: 'h2', //一级标题 levelTwo: 'h3', //二级标题(暂不支持更多级) width: 160, //容器宽度 height: '100%', //容器高度 padding: 10, //内部间距 offTop: 130, //滚动切换导航时离顶部的距离 }); h1_length = $("#post-content h1").length // console.log(h1_length); h2_length = $("#post-content h2").length // console.log(h2_length); h3_length = $("#post-content h3").length // console.log(h3_length); if (h2_length < 1) { $(".blog-auto-menu-div").remove(); } else { $(".blog-auto-menu-div-title").show(); } }); </script> <script src="/static/v3_theme_01_asset/js/headBand.min.js"></script> <script> $('#geek-nav').headBand({ 'background': '#222222',//设置背景色 'height': "2" //设置进度条高度 }); </script></body> </html><script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="58f19274b61f8be814abd200-|49" defer></script>