閒來無事,寫了一個當下比較常見的下拉刷新/上拉加載的jquery插件,代碼記錄在這裡,有興趣將代碼寫成插件與npm包可以留言。
體驗地址:http://owenliang.github.io/pullToRefresh/
項目地址:https://github.com/owenliang/pullToRefresh
實現注意:
利用transition做動畫時,優先使用transform:translate取代top,後者動畫流暢度存在問題。
各移動浏覽器對手勢觸摸的處理不同(簡單羅列如下),但是下面的應對方案又會導致部分浏覽器的overflow:scroll失效,總之難以兼容:
微信浏覽器下拉自帶回彈動畫:可以禁止document的touchmove事件默認處理行為。
谷歌浏覽器下拉自帶刷新功能:利用屬性touch-action: none可以禁掉。
針對上述問題,我的建議是滾動一律用iscroll5插件模擬實現(非overflow:scroll),然後利用上面的方法禁掉浏覽器的默認touchmove行為。
transition如果有多個屬性,那麼transitionend回調會為每個屬性回調一次,因此遇到其中任意一個回調就應該把css和transitionend回調都刪除掉。
浏覽器在執行JS代碼時沒有機會重繪UI,因此在使用transition的時候一定要注意把修改動畫終止CSS的代碼通過setTimeout延遲一會執行。
貼代碼上首頁,歡迎留言交流,需一位有興趣有時間的朋友合作,主要做2件事:
1)插件改為NPM包。
2)基於pullToRefresh庫,開發類似"今日頭條"的左右滑動UI。
pullToRefresh.js:
/** * 為指定的容器添加滾動條,支持下拉刷新與上拉加載功能 * @param container 需要滾動的容器,要求設置css: position!=static,height= * @param option 配置項,詳見下方defaultOption說明 * @return 返回對象用於操控此區域,當前暴露了iscroll的refresh函數,當你在插件之外向滾動區域增加/刪除內容後應該主動調用一次 * @description * * 2017-03-29 * 1)支持上拉加載 * 2017-03-30 * 1)改為jquery靜態函數插件 * 2)支持關閉下拉刷新或上拉加載 */ $.installPullToRefresh = function (container, option) { // 起始觸摸位置 var touchStartY = 0; // 起始圖標位置 var pullStartY = 0; // 當前的觸摸事件 var touchEvent = null; // 當前的刷新事件 var refreshEvent = null; // 當前圖標位置 var curY = -55; // 當前的加載事件 var loadEvent = null; // 默認參數 var defaultOption = { // 刷新相關 noRefresh: false, // 關閉下拉刷新特性 pauseBound: 40, // 觸發刷新的位置(也是圖標loading暫停的位置) lowerBound: 80, // 最大下拉到多少px loadImg: "load.png", // loading圖片 pullImg: "pull.png", // 下拉圖片 onRefresh: function (refreshDone) { // 刷新數據回調 setTimeout(function() { // 默認不做任何事 refreshDone(); }, 0); }, // 加載相關 noLoad: false, // 關閉上拉加載特性 bottomHeight: 1, // 距離滾動條底部多少px發起刷新 onLoad: function (loadDone) { setTimeout(function() { loadDone(); }, 0); }, }; var finalOption = $.extend(true, defaultOption, option); // 創建iscroll5滾動區域 var iscroll = new IScroll(container, { bounce: false, }); // 關閉上拉加載特性 if (!finalOption.noLoad) { // 監聽滾動結束事件,用於上拉加載 iscroll.on('scrollEnd', function () { // 有滾動條的情況下,才允許上拉加載 if (iscroll.maxScrollY < 0) { // maxScrollY<0表明出現了滾動條 var bottomDistance = (iscroll.maxScrollY - iscroll.y) * -1; // 距離底部足夠近,觸發加載 if (bottomDistance <= finalOption.bottomHeight) { // 當前沒有刷新和加載事件正在執行 if (!loadEvent && !refreshEvent) { loadEvent = {}; // 生成新的加載事件 finalOption.onLoad(function (error, msg) { loadEvent = null; // 清理當前的加載事件 // 延遲重繪滾動條 setTimeout(function () { iscroll.refresh(); }, 0); }); } } } }); } // 關閉下拉刷新特性 if (!finalOption.noRefresh) { // 緊鄰滾動區域,容納刷新圖標 var pullContainer = $('<div class="pullContainer"></div>') // 創建小圖標 var pullToRefresh = $('<div class="pullToRefresh"><img src="' + finalOption.pullImg + '"></div>'); // 保留小圖標的快捷方式 var pullImg = pullToRefresh.find("img"); // 小圖標加入到容器 pullContainer.append(pullToRefresh); // 小圖標容器添加到滾動區域之前 $(container).before(pullContainer); // 預加載loadImg $('<img src="' + finalOption.loadImg + '">'); // 設置transform的函數 function cssTransform(node, content) { node.css({ '-webkit-transform' : content, '-moz-transform' : content, '-ms-transform' : content, '-o-transform' : content, 'transform' : content, }); } // 調整小圖標位置,角度,透明度 function goTowards(translateY, rotate, opcaticy) { // 更新當前小圖標的位置,獲取css(transform)比較麻煩,所以每次變更時自己保存 curY = translateY; // 旋轉圖標(根據抵達lowerBound的比例旋轉,最大轉1圈) if (rotate === undefined) { rotate = (curY / finalOption.lowerBound) * 360; } // 透明度根據抵達pauseBound的比例計算 if (opcaticy === undefined) { opcaticy = (curY / finalOption.pauseBound) * 1; if (opcaticy > 1) { opcaticy = 1; } } // 改變位置和旋轉角度 cssTransform(pullToRefresh, "translateY(" + translateY + "px) translateZ(0)" + "rotateZ(" + rotate + "deg)"); // 改變透明度 pullToRefresh.css("opacity", opcaticy); } // 開啟回彈動畫 function tryStartBackTranTop() { // 啟動回彈動畫 pullToRefresh.addClass("backTranTop"); // 判斷是否觸發刷新 if (curY >= finalOption.pauseBound) { goTowards(finalOption.pauseBound); // 回彈動畫結束發起刷新 pullToRefresh.on('transitionend webkitTransitionEnd oTransitionEnd', function (event) { // 由於transitionend會對每個屬性回調一次,所以只處理其中一個 if (event.originalEvent.propertyName == "transform") { // 暫停動畫 pullToRefresh.removeClass("backTranTop"); pullToRefresh.unbind(); // 透明度重置為1 goTowards(finalOption.pauseBound, undefined, 1); // 切換圖片為loading圖 pullImg.attr("src", finalOption.loadImg); // 因為anamition會覆蓋transform的原因,使用top臨時定位元素 pullToRefresh.addClass("loadingAnimation"); pullToRefresh.css("top", finalOption.pauseBound + "px"); // 回調刷新數據,最終應將refreshEvent傳回校驗 finalOption.onRefresh(function (error, msg) { // 用戶回調時DOM通常已經更新, 需要通知iscroll調整(官方建議延遲執行,涉及到浏覽器重繪問題) setTimeout(function () { iscroll.refresh(); }, 0); // 重置角度,切換為pull圖 goTowards(finalOption.pauseBound); // 取消animation,重置top pullToRefresh.removeClass("loadingAnimation"); pullToRefresh.css("top", ""); // 延遲過渡動畫100毫秒,給浏覽器重繪的機會 setTimeout(function () { // 切換為pull圖 pullImg.attr("src", finalOption.pullImg); // 恢復動畫 pullToRefresh.addClass("backTranTop"); // 刷新完成 refreshEvent = null; // 彈回頂部 goTowards(-55); }, 100); }); } }); } else { goTowards(-55); // 彈回頂部 refreshEvent = null; // 未達成刷新觸發條件 } } // 父容器注冊下拉事件 $(container).on("touchstart", function (event) { // 新的觸摸事件 touchEvent = {}; // 有一個刷新事件正在進行 if (refreshEvent) { return; } // 只有滾動軸位置接近頂部, 才可以生成新的刷新事件 if (iscroll.y < -1 * finalOption.lowerBound) { return; } // 一個新的刷新事件 refreshEvent = touchEvent; touchStartY = event.originalEvent.changedTouches[0].clientY; pullStartY = curY; // 如果存在,則關閉回彈動畫與相關監聽 pullToRefresh.removeClass("backTranTop"); pullToRefresh.unbind(); // 切換為pull圖 pullImg.attr("src", finalOption.pullImg); }).on("touchmove", function (event) { // 在刷新未完成前觸摸,將被忽略 if (touchEvent != refreshEvent) { return; } var touchCurY = event.originalEvent.changedTouches[0].clientY; var touchDistance = touchCurY - touchStartY; // 本次移動的距離 var curPullY = pullStartY + touchDistance; // 計算圖標應該移動到的位置 // 向下不能拉出范圍 if (curPullY > finalOption.lowerBound) { curPullY = finalOption.lowerBound; } // 向上不能拉出范圍 if (curPullY <= -55) { curPullY = -55; } // 更新圖標的位置 goTowards(curPullY); }).on("touchend", function (event) { // 在刷新未完成前觸摸,將被忽略 if (touchEvent != refreshEvent) { return; } // 嘗試啟動回彈動畫 tryStartBackTranTop(); }); } // 初始化iscroll setTimeout(function() { iscroll.refresh(); }, 0); // 返回操作此區域的工具對象 return { // 用戶如果在下拉刷新之外修改了滾動區域的內容,需要主動調用refresh refresh: function() { // 延遲以便配合浏覽器重繪 setTimeout(function() { iscroll.refresh(); }, 0); }, // 觸發下拉刷新 triggerPull: function() { // 正在刷新或者禁止刷新 if (refreshEvent || finalOption.noRefresh) { return false; } // 暫停可能正在進行的最終階段回彈動畫 pullToRefresh.removeClass("backTranTop"); // 小圖標移動到lowerbound位置 goTowards(finalOption.lowerBound); // 創建新的刷新事件,占坑可以阻止在setTimeout之前的觸摸引起刷新 refreshEvent = {}; // 延遲到浏覽器重繪 setTimeout(function() { tryStartBackTranTop(); }, 100); }, }; }; Contact GitHub API Training Shop Blog About © 2017 GitHub, Inc. Terms Privacy Security Status Help
pullToRefresh.css:
.pullToRefresh { position:absolute; left:0; right:0; margin:auto; width: 50px; height: 50px; z-index: 10; opacity: 1; transform:translateY(-55px) translateZ(0) rotateZ(0deg); -ms-transform:translateY(-55px) translateZ(0) rotateZ(0deg); /* IE 9 */ -moz-transform:translateY(-55px) translateZ(0) rotateZ(0deg); /* Firefox */ -webkit-transform:translateY(-55px) translateZ(0) rotateZ(0deg); /* Safari 和 Chrome */ -o-transform:translateY(-55px) translateZ(0) rotateZ(0deg); /* Opera */ } .backTranTop { transition: transform 0.8s ease, opacity 0.8s ease; -moz-transition: transform 0.8s ease, opacity 0.8s ease; /* Firefox 4 */ -webkit-transition: transform 0.8s ease, opacity 0.8s ease; /* Safari 和 Chrome */ -o-transition: transform 0.8s ease, opacity 0.8s ease; /* Opera */ } .pullContainer { position:relative; } .pullToRefresh img { display:block; width: 40px; height: 40px; /* 讓img居中在.pullToRefresh中 */ position: absolute; top: 0; bottom: 0; left:0; right:0; margin:auto; } /* loading旋轉動畫 */ .loadingAnimation { animation: loadingFrame 1s infinite; -moz-animation: loadingFrame 1s infinite; /* Firefox */ -webkit-animation: loadingFrame 1s infinite; /* Safari 和 Chrome */ -o-animation: loadingFrame 1s infinite; /* Opera */ } @keyframes loadingFrame { from { transform: rotateZ(360deg); } to { transform: rotateZ(0deg); } } @-moz-keyframes loadingFrame /* Firefox */ { from { transform: rotateZ(360deg); } to { transform: rotateZ(0deg); } } @-webkit-keyframes loadingFrame /* Safari 和 Chrome */ { from { transform: rotateZ(360deg); } to { transform: rotateZ(0deg); } } @-o-keyframes loadingFrame /* Opera */ { from { transform: rotateZ(360deg); } to { transform: rotateZ(0deg); } }
以上所述是小編給大家介紹的JS+CSS實現下拉刷新/上拉加載插件,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對網站的支持!