上次在 Amazon 的 A9 Search 看到一個很有趣的 AJAX 應用,看了就很想自己實作看看,實際上也不困難,如果瞭解 AJAX 的原理,Javascript event 處理等就能寫出來了,所以在這裡介紹給大家看看。
[Amazon A9 Search]
傳統的搜尋引擎如 Google 或 Yahoo 等,在將搜尋結果丟出來後,會以分頁來顯示這些結果,使用者想要找的結果如果不是在第一頁的話,就會一頁接著一頁向後找尋。這種作法也沒什麼不對,很直覺,但是我看了 A9 的用法後,我反而覺得 A9 的作法更為實際,更加直覺。A9 怎麼操作的? Scrollbar !

A9 會先丟出前幾筆的結果於網頁上,例如排名前 20 筆,但是當妳使用 Scrollbar 往下拉動時,更多資料就會源源不絕的顯示出來,同時妳也會發現左邊的 Scrollbar 越來越小,代表資料量越來越多。用寫的來描述這行為實在很難,妳可以上 http://www.a9.com ,透過實際操作便可瞭解。為什麼說他更直覺呢?因為如果第一次沒看到你想要的資料時,同時妳又看到 scrollbar 在旁邊,直覺上的操作就是會去拉動 scrollbar ,如同妳在 google 的搜尋結果中,也會拉動瀏覽器視窗的 scrollbar 來看底下的資料,如果沒有再點下一頁,所以既然都在拉動 scrollbar 了,何不在同時也去抓新的資料,所以我說他的操作方式更加直覺。
[怎麼做?]
假設我們想讓內容顯示在一 DIV 區塊中,藉由使用者拖拉 scrollbar 來動態增加顯示內容的話:
第一步、於 DIV 區塊上顯示 scrollbar
這很簡單,只需要透過簡單的 style 屬性設定即可,前提就是妳必須先設定 DIV 區塊高度,並設定屬性 overflow:auto ,例如:
第二步、攔截使用者 scrolling 動作
檢而言之,攔截該 DIV 區塊的 onscroll 事件,例如:
第三步、呼叫 AJAX,向伺服器要新資料,例如:
第四步、取得 AJAX 回傳結果,也就是上述程式碼中的 showResponse 函式:
[Refactoring]
我們將這樣的概念稍微把他物件化,讓它可以重複被使用,而且增加一些保護措施。
這裡頭有個 ScrollNav 物件,就是將這些東西都包進去了,此外,於 checkScroll 函式中會檢查 event 是否被重複攔截,以及檢查目前 scrollbar 是否被拉動至最下方,唯有 scrollbar 被拉到最下面時,我們才向伺服器要求更多的資料,避免頻寬資源的浪費。scrollTop 跟 scrollHeight 都是內建的屬性,scrollTop 代表目前顯示的 view 距離最上方多少有 pixel,而 scrollHeight 則代表內容 view 的總長,同樣是 pixel,當妳資料越塞越多時,scrollTop 跟 scrollHeight 都會跟著增加,如果要判斷 scrollbar 是否為拉到最下方,最簡單的方法便是計算 scrollTop 和 DIV 區塊的高度總長是否等於 scrollHeight 即可。
至於在 showResponse() 函式中,我們會在每次抓到的新資料中都塞進個「Fetching ...」的字眼,代表目前正在抓取更多資料,這是一種小技巧,就像在大部分的 AJAX Pattern 中都可以看到的 Loading 字眼,妳也可以用個 gif 動畫來取代,只不過每次取得新資料時,必須將上一個「Fetching ...」移除。
想看結果嗎?請看 Demo 。裡頭有兩個 DIV 區塊,左邊的 DIV 區塊所 request 的 PHP 檔執行較快,而右邊的每次都會 sleep 5 秒鐘,所以感覺起來比較慢,拉動兩個 scrollbar 妳便可以發覺兩者間的不同。
[其他問題?]
目前這個範例還有些存在性的問題:
[這可以應用在哪?]< br/> 我想這些概念,實作都是很簡單,重要的是妳有沒有創意,有沒有想到這些 idea,以及怎麼應用在妳的網頁中,這我想應該讓妳自己去想想看,不過我覺得可以應用在某些功能上,例如 blog 留言版,留言版並不是每個人都想看,如果留言眾多時,每次都將留言塞到使用者的瀏覽器上也不甚合理,如果透過類似的方法,先讓使用者看到最新的留言,如果想繼續看下去,只需要拉動 scrollbar 即可。應該還可以想出更多有趣的應用,不過還是大家自己想想看吧。
[Amazon A9 Search]
傳統的搜尋引擎如 Google 或 Yahoo 等,在將搜尋結果丟出來後,會以分頁來顯示這些結果,使用者想要找的結果如果不是在第一頁的話,就會一頁接著一頁向後找尋。這種作法也沒什麼不對,很直覺,但是我看了 A9 的用法後,我反而覺得 A9 的作法更為實際,更加直覺。A9 怎麼操作的? Scrollbar !

A9 會先丟出前幾筆的結果於網頁上,例如排名前 20 筆,但是當妳使用 Scrollbar 往下拉動時,更多資料就會源源不絕的顯示出來,同時妳也會發現左邊的 Scrollbar 越來越小,代表資料量越來越多。用寫的來描述這行為實在很難,妳可以上 http://www.a9.com ,透過實際操作便可瞭解。為什麼說他更直覺呢?因為如果第一次沒看到你想要的資料時,同時妳又看到 scrollbar 在旁邊,直覺上的操作就是會去拉動 scrollbar ,如同妳在 google 的搜尋結果中,也會拉動瀏覽器視窗的 scrollbar 來看底下的資料,如果沒有再點下一頁,所以既然都在拉動 scrollbar 了,何不在同時也去抓新的資料,所以我說他的操作方式更加直覺。
[怎麼做?]
假設我們想讓內容顯示在一 DIV 區塊中,藉由使用者拖拉 scrollbar 來動態增加顯示內容的話:
第一步、於 DIV 區塊上顯示 scrollbar
這很簡單,只需要透過簡單的 style 屬性設定即可,前提就是妳必須先設定 DIV 區塊高度,並設定屬性 overflow:auto ,例如:
<div id="content" style="width:490px;height:490px; overflow: auto">當妳於 DIV 區塊中的內容超過 DIV 高度時,瀏覽器便會於右邊出現 scrollbar。
.... 妳想要顯示的內容。
</div>
第二步、攔截使用者 scrolling 動作
檢而言之,攔截該 DIV 區塊的 onscroll 事件,例如:
// 注意:以下程式碼使用 prototype.js 語法checkScoll 函式便是我們想要處理 scrolling event 的函式。
$('content').onscroll = checkScroll;
第三步、呼叫 AJAX,向伺服器要新資料,例如:
// 注意:以下程式碼使用 prototype.js 物件
var opts = new Object;
opts.parameters = 'page=' + page_num;
opts.onComplete = showResponse;
var ajax = new Ajax.Request (url, opts);
page_num ++;
第四步、取得 AJAX 回傳結果,也就是上述程式碼中的 showResponse 函式:
function showResponse(origReq) {看,這樣就完成了!超簡單!所以來 re-factoring 一下。
$('content').innerHTML += origReq.responseText;
}
[Refactoring]
我們將這樣的概念稍微把他物件化,讓它可以重複被使用,而且增加一些保護措施。
function showResponse(origReq) {
var nav = $(this.nav.contentID);
var last = nav.lastChild;
while (last.nodeName != 'DIV') {
last = last.previousSibling;
}
nav.removeChild (last);
nav.innerHTML += origReq.responseText;
nav.innerHTML += '<div style="background: #AAAAAA;">Fetching ... </div>';
this.fetching = false;
}
function ScrollNav(contentID, url) {
this.contentID = contentID;
this.contentObj = $(contentID);
this.contentObj.nav = this; // 記錄目前物件,給 checkScroll 函式使用
this.fetching = false;
this.scrollTop = this.contentObj.scrollTop; // 記錄一開始 scrollTop
this.scrollHeight = this.contentObj.scrollHeight; // 記錄一開始 scrollHeight
this.url = url;
this.idx = 0; // page number
this.checkScroll = function() {
if(this.nav.scrollTop == this.scrollTop)
return;
if (this.fetching)
return;
this.nav.scrollTop = this.scrollTop;
if (this.scrollHeight == parseInt(this.style.height) + this.scrollTop) {
var opts = new Object;
opts.parameters = 'page=' + this.nav.idx;
opts.onComplete = showResponse.bind(this);
var ajax = new Ajax.Request(this.nav.url, opts);
this.fetching = true;
this.nav.idx ++;
}
}
// 攔截 onscroll event
this.contentObj.onscroll = this.checkScroll;
}
function init() {
var nav = new ScrollNav('content', 'scroll_test.php');
var nav = new ScrollNav('content2', 'scroll_test2.php');
}
這裡頭有個 ScrollNav 物件,就是將這些東西都包進去了,此外,於 checkScroll 函式中會檢查 event 是否被重複攔截,以及檢查目前 scrollbar 是否被拉動至最下方,唯有 scrollbar 被拉到最下面時,我們才向伺服器要求更多的資料,避免頻寬資源的浪費。scrollTop 跟 scrollHeight 都是內建的屬性,scrollTop 代表目前顯示的 view 距離最上方多少有 pixel,而 scrollHeight 則代表內容 view 的總長,同樣是 pixel,當妳資料越塞越多時,scrollTop 跟 scrollHeight 都會跟著增加,如果要判斷 scrollbar 是否為拉到最下方,最簡單的方法便是計算 scrollTop 和 DIV 區塊的高度總長是否等於 scrollHeight 即可。
至於在 showResponse() 函式中,我們會在每次抓到的新資料中都塞進個「Fetching ...」的字眼,代表目前正在抓取更多資料,這是一種小技巧,就像在大部分的 AJAX Pattern 中都可以看到的 Loading 字眼,妳也可以用個 gif 動畫來取代,只不過每次取得新資料時,必須將上一個「Fetching ...」移除。
想看結果嗎?請看 Demo 。裡頭有兩個 DIV 區塊,左邊的 DIV 區塊所 request 的 PHP 檔執行較快,而右邊的每次都會 sleep 5 秒鐘,所以感覺起來比較慢,拉動兩個 scrollbar 妳便可以發覺兩者間的不同。
[其他問題?]
目前這個範例還有些存在性的問題:
- 別吃光 browser 的記憶體了。塞資料時要設立停止點,別一直狂塞,吃光妳瀏覽器的記憶體了。
- 不一定要拉動到最下方才開始抓取資料,可以在使用者的 scrollbar 接近最下方時,便向伺服器要求新資料,可以達到更順暢的操作。
[這可以應用在哪?]< br/> 我想這些概念,實作都是很簡單,重要的是妳有沒有創意,有沒有想到這些 idea,以及怎麼應用在妳的網頁中,這我想應該讓妳自己去想想看,不過我覺得可以應用在某些功能上,例如 blog 留言版,留言版並不是每個人都想看,如果留言眾多時,每次都將留言塞到使用者的瀏覽器上也不甚合理,如果透過類似的方法,先讓使用者看到最新的留言,如果想繼續看下去,只需要拉動 scrollbar 即可。應該還可以想出更多有趣的應用,不過還是大家自己想想看吧。


