Register Register Member Login Member Login Member Login Forgot Password ??
PHP , ASP , ASP.NET, VB.NET, C#, Java , jQuery , Android , iOS , Windows Phone
 

Registered : 108,597

HOME > Client Script Forum > แจก scroll pagination (infinite scroll) โหลดข้อมูลจากหน้าใหม่เมื่อสกรอลล์ลงไปเรื่อยๆ



 

แจก scroll pagination (infinite scroll) โหลดข้อมูลจากหน้าใหม่เมื่อสกรอลล์ลงไปเรื่อยๆ

 



Topic : 135937



โพสกระทู้ ( 4,201 )
บทความ ( 8 )



สถานะออฟไลน์




คุณสมบัติ:
- scroll ลงเพื่อโหลดข้อมูลจากหน้าใหม่อัตโนมัติ
- เปลี่ยน URL parameter ?start=n ได้ เพื่อเวลากด back กลับมาหรือ reload จะได้ไม่ต้องไปเริ่มใหม่ โดยเริ่มจากที่มันคงอยู่บน URL ได้เลย
- มี events ต่างๆให้ดักสำหรับการสร้าง HTML เพื่อแสดงข้อมูลที่โหลดมา แบบกำหนดเอง

สิ่งที่ยังไม่ได้ทำ:
- ซ่อน element ที่มันเลยขอบเขตการแสดงผลออกไป เพื่อให้ลดภาระการโหลดหรือทำงานของเบราว์เซอร์ กรณีโหลดข้อมูลมามากๆมหาศาล


ScrollPagination.js
Code (JavaScript)
/**
 * Scroll pagination.
 * 
 * @author Vee W.
 * @license MIT.
 */


/**
 * Scroll pagination (infinite scroll).
 * 
 * @todo Hide overflow elements on top and bottom.
 */
class ScrollPagination {


    /**
     * Class constructor.
     * 
     * @param {object} option 
     */
    constructor(option = {}) {
        if (!option.containerSelector) {
            option.containerSelector = '#container';
        }
        this.containerSelector = option.containerSelector;

        if (!option.bottomOffset) {
            // the bottom offset where scroll almost to very bottom of page.
            option.bottomOffset = 30;
        }
        this.bottomOffset = option.bottomOffset;

        // change url options ----------------------------
        if (!option.changeURL) {
            option.changeURL = true;
        }
        this.changeURL = option.changeURL;

        if (!option.changeURLParamStartOffset) {
            // querystring for start offset to push to the URL.
            // example ?rdspStartOffset=10 when scroll to next page from first while displaying 10 items per page.
            option.changeURLParamStartOffset = 'rdspStartOffset';
        }
        this.changeURLParamStartOffset = option.changeURLParamStartOffset;
        // end change url options -------------------------

        // ajax options -----------------------------------
        if (!option.ajaxUrl) {
            // set ajax url with `%startoffset%` to use as start offset.
            // example: http://domain.tld/page?offset=%startoffset%
            throw new Error('The `ajaxUrl` property is missing.');
        }
        this.ajaxUrl = option.ajaxUrl;

        if (!option.ajaxMethod) {
            option.ajaxMethod = 'GET';
        }
        this.ajaxMethod = option.ajaxMethod;

        if (!option.ajaxData) {
            // the ajax data to send with some methods such as POST, PATCH, PULL, DELETE, etc.
            // the data will be like name=value&name2=value2 or get the data from the `FormData()` object.
            // the string that contain `%startoffset%` will be replace with start offset.
            option.ajaxData = '';
        }
        this.ajaxData = option.ajaxData;

        if (!option.ajaxAccept) {
            option.ajaxAccept = 'application/json';
        }
        this.ajaxAccept = option.ajaxAccept;

        // response type for accept. possible values:
        // text/html -> response
        // application/xml -> responseXML
        // application/json -> responseText
        // text/plain -> responseText
        // application/javascript, application/xxxscript -> responseText
        if (!option.ajaxResponseAcceptType) {
            option.ajaxResponseAcceptType = 'responseText';
        }
        this.ajaxResponseAcceptType = option.ajaxResponseAcceptType;

        if (!option.ajaxContentType) {
            option.ajaxContentType = 'application/x-www-form-urlencoded;charset=UTF-8';
        }
        this.ajaxContentType = option.ajaxContentType;

        if (!option.ajaxDataSrc) {
            // set data source for count how many items retrieved for set new start offset.
            option.ajaxDataSrc = 'items';
        }
        this.ajaxDataSrc = option.ajaxDataSrc;
        // end ajax options -------------------------------

        this.currentStartOffset = this.detectAndSetCurrentStartOffset();
        this.callingXHR = false;
        this.isScrolling = '';// up or down.
        this.XHR = new Promise((resolve, reject) => {});
    }// constructor


    /**
     * AJAX pagination.
     * 
     * @private This method was called from `checkScrollAndMakeXHR()`.
     */
    ajaxPagination() {
        let thisClass = this;

        let promiseObj = new Promise((resolve, reject) => {
            if (thisClass.callingXHR === true) {
                return reject('previous XHR is calling.');
            }

            thisClass.callingXHR = true;

            let XHR = new XMLHttpRequest();

            XHR.addEventListener('error', (event) => {
                thisClass.callingXHR = false;
                reject({'response': '', 'status': (event.currentTarget ? event.currentTarget.status : ''), 'event': event});
            });
            XHR.addEventListener('loadstart', (event) => {
                let response = (event.currentTarget ? event.currentTarget : event);
                document.dispatchEvent(
                    new CustomEvent(
                        'rdScrollPagination.start', {'detail': response}
                    )
                );
            });
            XHR.addEventListener('loadend', (event) => {
                let response = (event.currentTarget ? event.currentTarget[thisClass.ajaxResponseAcceptType] : '');
                if (thisClass.ajaxAccept.toLowerCase().includes('/json')) {
                    try {
                        if (response) {
                            response = JSON.parse(response);
                        }
                    } catch (exception) {
                        console.error(exception.message, response);
                    }
                }

                let headers = XHR.getAllResponseHeaders();
                let headerMap = {};
                if (headers) {
                    let headersArray = headers.trim().split(/[\r\n]+/);
                    headersArray.forEach((line) => {
                        let parts = line.split(': ');
                        let header = parts.shift();
                        let value = parts.join(': ');
                        headerMap[header] = value;
                    });
                    headersArray = undefined;
                }
                headers = undefined;

                if (response[thisClass.ajaxDataSrc] && response[thisClass.ajaxDataSrc].length > 0) {
                    // if there are items after XHR.
                    // append pagination data element.
                    thisClass.appendPaginationDataElement();
                    // set next start offset.
                    thisClass.currentStartOffset = parseInt(thisClass.currentStartOffset) + parseInt(response[thisClass.ajaxDataSrc].length);
                    // mark calling to false to allow next pagination call.
                    thisClass.callingXHR = false;// move in here to prevent ajax call again when there are no more data.
                }

                if (event.currentTarget && event.currentTarget.status >= 100 && event.currentTarget.status < 400) {
                    resolve({'response': response, 'status': event.currentTarget.status, 'event': event, 'headers': headerMap});
                } else {
                    reject({'response': response, 'status': event.currentTarget.status, 'event': event, 'headers': headerMap});
                }
            });

            XHR.open(thisClass.ajaxMethod, thisClass.ajaxUrl.replace('%startoffset%', thisClass.currentStartOffset));
            XHR.setRequestHeader('Accept', thisClass.ajaxAccept);
            if (thisClass.ajaxContentType) {
                XHR.setRequestHeader('Content-Type', thisClass.ajaxContentType);
            }
            XHR.send(thisClass.ajaxData.replace('%startoffset%', thisClass.currentStartOffset));
        });
        thisClass.XHR = promiseObj;

        return promiseObj;
    }// ajaxPagination


    /**
     * Append pagination data element.
     * 
     * @private This method was called from `ajaxPagination()`.
     */
    appendPaginationDataElement() {
        let containerElement = document.querySelector(this.containerSelector);
        if (containerElement) {
            containerElement.insertAdjacentHTML(
                'beforeend', 
                '<div class="rd-scroll-pagination"'
                    + ' data-startoffset="' + this.currentStartOffset + '"'
                    //+ ' style="display:none;"'
                    + '></div>'
            );
        }
    }// appendPaginationDataElement


    /**
     * Check that scroll is near the display area and make XHR (AJAX).
     * 
     * @private This method was called from `listenOnScroll()`.
     * @param object event 
     */
    checkScrollAndMakeXHR(event) {
        let thisClass = this;
        let windowHeight = window.innerHeight;
        let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
        let documentScrollHeight = document.documentElement.scrollHeight;
        let totalScroll = (parseInt(windowHeight) + parseInt(scrollTop)) + parseInt(this.bottomOffset);

        if (totalScroll >= documentScrollHeight) {
            /*console.log(
                'total scroll >= document scroll height.',
                {
                    'totalScroll': totalScroll,
                    'documentScrollHeight': documentScrollHeight
                }
            );*/

            // begins ajax pagination.
            this.ajaxPagination()
            .then((responseObject) => {
                document.dispatchEvent(
                    new CustomEvent(
                        'rdScrollPagination.done', {'detail': responseObject}
                    )
                );
                thisClass.triggerOnScroll();

                return Promise.resolve(responseObject);
            })
            .catch((responseObject) => {
                // .catch() must be after .then(). see https://stackoverflow.com/a/42028776/128761
                document.dispatchEvent(
                    new CustomEvent(
                        'rdScrollPagination.fail', {'detail': responseObject}
                    )
                );

                return Promise.reject(responseObject)
                .then(() => {
                    // not called.
                }, (responseObject) => {
                    // prevent uncaught error.
                });
            });
        }
    }// checkScrollAndMakeXHR


    /**
     * Check scrolling up or down and change current URL.
     * 
     * @private This method was called from `listenOnScroll()`.
     * @param {object} event 
     */
    checkScrollAndChangeURL(event) {
        if (this.changeURL !== true) {
            return ;
        }

        let thisClass = this;
        let paginationDataElements = document.querySelectorAll('.rd-scroll-pagination');

        if (paginationDataElements) {
            paginationDataElements.forEach((item, index) => {
                let rect = item.getBoundingClientRect();
                if (rect.top >= 0 && rect.top < 30) {
                    // if scrolled and top of this pagination data element is on top within range (30 - for example).
                    // retrieve this start offset from `data-startoffset="n"`.
                    let thisStartOffset = item.dataset.startoffset;

                    // get all querystrings except start offset and re-assign the start offset.
                    const params = new URLSearchParams(window.location.search);
                    let paramObj = {};
                    for(let paramName of params.keys()) {
                        if (paramName !== thisClass.changeURLParamStartOffset) {
                            paramObj[paramName] = params.get(paramName);
                        }
                    }
                    paramObj[thisClass.changeURLParamStartOffset] = thisStartOffset;

                    // build querystring
                    let currentUrlNoQuerystring = window.location.href.split(/[?#]/)[0];
                    let queryString = Object.keys(paramObj).map((key) => {
                        return encodeURIComponent(key) + '=' + encodeURIComponent(paramObj[key])
                    }).join('&');
                    
                    // replace current URL.
                    window.history.replaceState(null, '', currentUrlNoQuerystring + '?' + queryString);
                    return;
                }
            });
        }
    }// checkScrollAndChangeURL


    /**
     * Detect and set current start offset from querystring.
     * 
     * @private This method was called from `constructor()`.
     * @return int Return detected number of start offset.
     */
    detectAndSetCurrentStartOffset() {
        const params = new URLSearchParams(window.location.search);
        let currentStartOffsetQuerystring = params.get(this.changeURLParamStartOffset);

        if (
            currentStartOffsetQuerystring === null ||
            currentStartOffsetQuerystring === '' || 
            isNaN(currentStartOffsetQuerystring) ||
            isNaN(parseFloat(currentStartOffsetQuerystring)) ||
            currentStartOffsetQuerystring < 0
        ) {
            currentStartOffsetQuerystring = 0;
        }

        return parseInt(currentStartOffsetQuerystring);
    }// detectAndSetCurrentStartOffset


    /**
     * Get XHR property object.
     * 
     * @return XMLHttpRequest
     */
    async getXHR() {
        return this.XHR;
    }// getXHR


    /**
     * Invoke, run the class.
     */
    invoke() {
        this.listenOnScroll();
    }// invoke


    /**
     * Listen on scroll window/element.
     * 
     * @private This method was called from `invoke()`.
     */
    listenOnScroll() {
        let thisClass = this;
        let lastScroll = 0;
        
        window.addEventListener('scroll', (event) => {
            let scrollTop = window.pageYOffset || document.documentElement.scrollTop;

            if (scrollTop >= lastScroll) {
                // scrolling down
                console.log('scrolling down');
                thisClass.isScrolling = 'down';
                thisClass.checkScrollAndMakeXHR(event);
                thisClass.checkScrollAndChangeURL(event);
            } else {
                // scrolling up
                console.log('scrolling up');
                thisClass.isScrolling = 'up';
                thisClass.checkScrollAndChangeURL(event);
            }

            lastScroll = (scrollTop <= 0 ? 0 : parseInt(scrollTop));
        }, false);

        this.triggerOnScroll();
    }// listenOnScroll


    /**
     * Trigger scroll event, 
     * best on initialize the class to trigger event 
     * and make ajax call while next pagination element is near the display area.
     * 
     * @private This method was called from `listenOnScroll()`.
     */
    triggerOnScroll() {
        window.dispatchEvent(new Event('scroll'));
    }// triggerOnScroll


}// ScrollPagination



styles.css (สำหรับทดลอง)
Code
* { box-sizing: border-box; } .posts-container { border: 1px dotted #ccc; display: block; margin: 20px auto; /*max-height: 90vh;*/ /*overflow-y: auto;*/ padding: 10px; width: 90vw; }



pagination-dummydata.php (สำหรับทดลอง)
Code (PHP)
<?php

$start = filter_input(INPUT_GET, 'start', FILTER_SANITIZE_NUMBER_INT);
if (!is_numeric($start) || $start < 0) {
    $start = 0;
}


$output = [];
if ($start < 200) {
    for ($i = 1; $i <= 10; $i++) {
        $output['items'][] = 'Item ' . ($start + $i) . ' - start: ' . $start . '; loop: ' . $i . '.';
    }
    usleep(300000);// 1000000 microseconds = 1 second
} else {
    http_response_code(200);
}


header('Content-type: application/json');
echo json_encode($output);
exit();



test01.html (สำหรับทดลอง)
Code
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS scroll pagination</title> <link rel="stylesheet" href="assets/css/styles.css"> </head> <body> <div id="posts-container" class="posts-container"></div> <script src="assets/js/ScrollPagination.js" type="application/javascript"></script> <script type="application/javascript"> // on window dom loaded. window.addEventListener('DOMContentLoaded', (event) => { // listen on ajax failed, done to render items. document.addEventListener('rdScrollPagination.start', (ajaxEvent) => { const loadingHtml = '<p class="item-loading">Loading &hellip;</p>'; document.querySelector('#posts-container').insertAdjacentHTML('beforeend', loadingHtml); }); document.addEventListener('rdScrollPagination.fail', (ajaxEvent) => { let loadingElement = document.querySelector('.item-loading'); if (loadingElement) { loadingElement.remove(); } }); document.addEventListener('rdScrollPagination.done', (ajaxEvent) => { let loadingElement = document.querySelector('.item-loading'); if (loadingElement) { loadingElement.remove(); } let response = (ajaxEvent.detail ? ajaxEvent.detail.response : {}); let listHtml = ''; if (response.items) { response.items.forEach((item, index) => { listHtml += '<p>' + item + '</p>'; }); } document.querySelector('#posts-container').insertAdjacentHTML('beforeend', listHtml); }); // initialize new scroll pagination class. let scrollPaginationClass = new ScrollPagination({ 'containerSelector': '#posts-container', 'ajaxUrl': 'pagination-dummydata.php?start=%startoffset%' }); // invoke/run the class. scrollPaginationClass.invoke(); }); </script> </body> </html>



rundiz scroll pagination.



Tag : HTML5, JavaScript, Ajax









ประวัติการแก้ไข
2021-02-24 16:23:47
Move To Hilight (Stock) 
Send To Friend.Bookmark.
Date : 2021-02-23 15:14:24 By : mr.v View : 25 Reply : 2
 

 

No. 1



โพสกระทู้ ( 199 )
บทความ ( 0 )



สถานะออฟไลน์


ขอบคุณสำหรับ open source ดีๆ ครับ
เมื่อมีเวลาจะลองนำไปศึกษาเพื่อประยุกต์ใช้ต่อไป







แสดงความคิดเห็นโดยอ้างถึง ความคิดเห็นนี้
Date : 2021-02-23 20:00:24 By : lakornworld
 


 

No. 2



โพสกระทู้ ( 4,201 )
บทความ ( 8 )



สถานะออฟไลน์


ตอบความคิดเห็นที่ : 1 เขียนโดย : lakornworld เมื่อวันที่ 2021-02-23 20:00:24
รายละเอียดของการตอบ ::
update ล่าสุดที่นี่...
https://rundiz.com/resources/downloads/scroll-pagination
https://github.com/Rundiz/rundiz-scroll-pagination
https://www.npmjs.com/package/rundiz-scroll-pagination

แสดงความคิดเห็นโดยอ้างถึง ความคิดเห็นนี้
Date : 2021-02-23 20:05:14 By : mr.v
 

   

ค้นหาข้อมูล


   
 

แสดงความคิดเห็น
Re : แจก scroll pagination (infinite scroll) โหลดข้อมูลจากหน้าใหม่เมื่อสกรอลล์ลงไปเรื่อยๆ
 
 
รายละเอียด
 
ตัวหนา ตัวเอียง ตัวขีดเส้นใต้ ตัวมีขีดกลาง| ตัวเรืองแสง ตัวมีเงา ตัวอักษรวิ่ง| จัดย่อหน้าอิสระ จัดย่อหน้าชิดซ้าย จัดย่อหน้ากึ่งกลาง จัดย่อหน้าชิดขวา| เส้นขวาง| ขนาดตัวอักษร แบบตัวอักษร
ใส่แฟลช ใส่รูป ใส่ไฮเปอร์ลิ้งค์ ใส่อีเมล์ ใส่ลิ้งค์ FTP| ใส่แถวของตาราง ใส่คอลัมน์ตาราง| ตัวยก ตัวห้อย ตัวพิมพ์ดีด| ใส่โค้ด ใส่การอ้างถึงคำพูด| ใส่ลีสต์
smiley for :lol: smiley for :ken: smiley for :D smiley for :) smiley for ;) smiley for :eek: smiley for :geek: smiley for :roll: smiley for :erm: smiley for :cool: smiley for :blank: smiley for :idea: smiley for :ehh: smiley for :aargh: smiley for :evil:
Insert PHP Code
Insert ASP Code
Insert VB.NET Code Insert C#.NET Code Insert JavaScript Code Insert C#.NET Code
Insert Java Code
Insert Android Code
Insert Objective-C Code
Insert XML Code
Insert SQL Code
Insert Code
เพื่อความเรียบร้อยของข้อความ ควรจัดรูปแบบให้พอดีกับขนาดของหน้าจอ เพื่อง่ายต่อการอ่านและสบายตา และตรวจสอบภาษาไทยให้ถูกต้อง

อัพโหลดแทรกรูปภาพ

Notice

เพื่อความปลอดภัยของเว็บบอร์ด ไม่อนุญาติให้แทรก แท็ก [img]....[/img] โดยการอัพโหลดไฟล์รูปจากที่อื่น เช่นเว็บไซต์ ฟรีอัพโหลดต่าง ๆ
อัพโหลดแทรกรูปภาพ ให้ใช้บริการอัพโหลดไฟล์ของไทยครีเอท และตัดรูปภาพให้พอดีกับสกรีน เพื่อความโหลดเร็วและไฟล์ไม่ถูกลบทิ้ง

   
  เพื่อความปลอดภัยและการตรวจสอบ กระทู้ที่แทรกไฟล์อัพโหลดไฟล์จากที่อื่น อาจจะถูกลบทิ้ง
 
โดย
อีเมล์
บวกค่าให้ถูก
<= ตัวเลขฮินดูอารบิก เช่น 123 (หรือล็อกอินเข้าระบบสมาชิกเพื่อไม่ต้องกรอก)







Exchange: Voake, Comcube, รับทำเว็บไซต์ รับเขียนโปรแกรม , pangpond.com , pangpond.co.th , สำนักงานบัญชี.com , รถมือสอง

Load balance : Server 01
ThaiCreate.Com Logo
© www.ThaiCreate.Com. 2003-2021 All Rights Reserved.
ไทยครีเอทบริการ จัดทำดูแลแก้ไข Web Application ทุกรูปแบบ (PHP, .Net Application, VB.Net, C#)
[Conditions Privacy Statement] ติดต่อโฆษณา 081-987-6107 อัตราราคา คลิกที่นี่