前言
接上期文章h5下拉刷新实现传统下拉场景之后,又去原生APP所遇场景并不仅仅是全屏范围的下拉刷新,有更多的场景则是部分元素下拉刷新,如列表的下拉刷新操作,so 我又来了,针对这种场景,优化一下我们的 pullRefresh,开始吧
效果展示
其一为局部元素列表下拉刷新效果
其二为页面整体下拉刷新效果
整体代码
话不多说放代码
/**
* @param {Object} options
* @使用方法
* const pullObj = pullRefresh(options);
* pullObj.init(); 初始化
*/
const parseTime = (timestamp) => {
const a = new Date(timestamp);
const h = a.getHours();
const s = a.getMinutes();
const double = t => t > 9 ? t : "0" + t;
return double(h) + ":" + double(s);
}
const loadingIcon = "loading.png";
const defaultOptions = {
parEl: document.body, // 父元素(不动的大盒子)
tarEl: "app", // 目标元素(滑动的子盒子)
pullCallback: () => { // 有效下拉回调,通常在内部获取数据并调用 finish() 方法
window.location.reload();
}
};
export function pullRefresh(_options) {
const options = {
...defaultOptions,
..._options
};
let lastUpdateTime = new Date().getTime();
let { parEl, tarEl, pullCallback } = options;
if(typeof parEl === "string") {
parEl = document.getElementById(parEl);
}
if(typeof tarEl === "string") {
tarEl = document.getElementById(tarEl);
}
const appStyleCache = tarEl.style;
const tarElPosition = appStyleCache.position;
const parTopDistance = parEl.getBoundingClientRect().top;
if(!tarElPosition) {
tarEl.style["position"] = "relative";
appStyleCache["position"] = "relative";
}
const pullTextEl = document.createElement("p");
pullTextEl.style = "width: 100%; text-align: center; position: absolute; z-index: 0; left: 0; top: -50px; font-size: 12px; line-height: 16px; color: #666; display: none;";
const loadImg = document.createElement("i");
loadImg.style = `display: inline-block; width: 15px; height: 15px; margin-right: 5px; background: url('${loadingIcon}'); background-size: 100% 100%; vertical-align: middle; animation: looprotate 2s linear infinite;`;
const loadText = document.createElement("span");
loadImg.style.opacity = 0;
// 是否执行有效下拉回调标志位
let refreshStatus = false;
const isQBrowser = () => { // qq系浏览器
const ua = navigator.userAgent.toLowerCase();
return !!ua.match(/mqqbrowser|qzone|qqbrowser/i);
};
const finish = () => {
if(isQBrowser()) return;
lastUpdateTime = new Date().getTime();
loadImg.style.opacity = 0;
loadText.innerText = `刷新成功 \n最后更新:${parseTime(lastUpdateTime)}`;
setTimeout(() => {
tarEl.style["transition"] = "transform 0.2s";
tarEl.style["transform"] = "translate(0, 0)";
setTimeout(() => {
pullTextEl.style["display"] = "none";
// tarEl.style = appStyleCache;
}, 400);
}, 500);
};
const init = () => {
if(isQBrowser()) return;
tarEl.appendChild(pullTextEl);
pullTextEl.appendChild(loadImg);
pullTextEl.appendChild(loadText);
let startP, moveLen;
// 下拉处理
const pullHandler = (moveLen) => {
// 下拉元素距离视口顶部距离
const tarTopDistance = tarEl.getBoundingClientRect().top;
// 下拉元素距离父级顶部距离
const toParTopDistance = tarTopDistance - parTopDistance;
// 有效下拉才做处理
if(toParTopDistance >= 0) {
if(pullTextEl.style["display"] === "none") {
// 有效下拉时才展示提示文案
pullTextEl.style["display"] = "block";
}
// 下拉效果
if(moveLen > 0 && moveLen < 50){
tarEl.style["transform"] = "translate(0, " + moveLen + "px)";
} else if(moveLen >= 50 && moveLen < 100) { // 到刷新标志,下拉阻尼增大
const _moveLen = 50 + (moveLen - 50) * 0.6;
tarEl.style["transform"] = "translate(0, " + _moveLen + "px)";
} else if(moveLen >= 100) { // 下拉超过 100,下拉阻尼再次增大
const _moveLen = (50 + ( 100 - 50)*0.6) + (moveLen - 100) * 0.2;
tarEl.style["transform"] = "translate(0, " + _moveLen + "px)";
}
// 下拉触发
if(toParTopDistance < 55) {
loadText.innerText = `下拉可以刷新... \n最后更新:${parseTime(lastUpdateTime)}`;
refreshStatus = false;
} else {
loadText.innerText = `松开立即刷新... \n最后更新:${parseTime(lastUpdateTime)}`;
refreshStatus = true;
}
}
};
tarEl.addEventListener("touchstart", (e) => {
startP = e.touches[0].pageY;
tarEl.style["transition"] = "transform 0s";
});
tarEl.addEventListener("touchmove", (e) => {
moveLen = e.touches[0].pageY - startP;
pullHandler(moveLen);
});
tarEl.addEventListener("touchend", () => {
// 下拉元素距离视口顶部距离
const tarTopDistance = tarEl.getBoundingClientRect().top;
// 下拉元素距离父级顶部距离
const toParTopDistance = tarTopDistance - parTopDistance;
if(toParTopDistance > 0) { // 当有效下拉发生后动画归位,重置样式
if(refreshStatus) {
loadImg.style.opacity = 1;
loadText.innerText = `正在刷新数据中... \n最后更新:${parseTime(lastUpdateTime)}`;
pullCallback && pullCallback();
tarEl.style["transition"] = "transform 0.4s";
tarEl.style["transform"] = "translate(0, 55px)";
} else {
tarEl.style["transition"] = "transform 0.4s";
tarEl.style["transform"] = "translate(0, 0)";
setTimeout(() => {
pullTextEl.style["display"] = "none";
tarEl.style = appStyleCache;
}, 400);
}
} else { // 未发生有效下拉的直接重置样式
pullTextEl.style["display"] = "none";
tarEl.style = appStyleCache;
}
});
};
return {
init,
finish
};
}
如何使用
本次优化更新了入参方式,将接受一个option对象
const pullRefreshObj = pullRefresh({
parEl: "pullParEl", // 作用元素父元素
tarEl: "pullTarEl", // 目标作用元素
pullCallback: async () => { // 产生有效下拉后释放触发回调方法
await yourHandler();
pullRefreshObj.finish();
}
});
pullRefreshObj.init(); // 初始化
结语
没错,继上次的初版下拉刷新,我们又成功俘获了用户的芳心,体验上去了,还愁留不住用户吗? 致敬每一位前端coder;Respect;