| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- /**
- * 下拉刷新通用工具 (BEXPullRefresh)
- *
- * 用法:
- * var ptr = BEXPullRefresh.init(document.getElementById('myContainer'), {
- * onRefresh: async function () { await fetchData(); },
- * texts: { pull: '下拉刷新', release: '释放刷新', refreshing: '刷新中...' },
- * threshold: 56
- * });
- *
- * // 手动销毁:ptr.destroy();
- * // 销毁全部:BEXPullRefresh.destroyAll();
- */
- (function () {
- 'use strict';
- var instances = [];
- function PTRInstance(container, options) {
- var opts = options || {};
- var threshold = opts.threshold || 56;
- var onRefresh = opts.onRefresh || function () { return Promise.resolve(); };
- var texts = opts.texts || {
- pull: 'Pull to refresh',
- release: 'Release to refresh',
- refreshing: 'Refreshing...'
- };
- var state = { pulling: false, refreshing: false, startY: 0, currentY: 0 };
- var indicatorEl = null;
- var spinnerEl = null;
- var textEl = null;
- function buildDOM() {
- indicatorEl = document.createElement('div');
- indicatorEl.className = 'ptr-indicator';
- indicatorEl.innerHTML =
- '<div class="ptr-spinner"></div>' +
- '<span class="ptr-text">' + texts.pull + '</span>';
- var parent = container.parentNode;
- parent.insertBefore(indicatorEl, container);
- spinnerEl = indicatorEl.querySelector('.ptr-spinner');
- textEl = indicatorEl.querySelector('.ptr-text');
- container.classList.add('ptr-container');
- // 确保父级支持 absolute 定位
- if (parent !== document.body) {
- var parentPos = window.getComputedStyle(parent).position;
- if (parentPos === 'static') parent.style.position = 'relative';
- }
- }
- function getScrollTop() {
- return document.documentElement.scrollTop || document.body.scrollTop || 0;
- }
- function applyTransform(y, noTransition) {
- if (noTransition) container.classList.add('ptr-pulling');
- else container.classList.remove('ptr-pulling');
- container.style.transform = 'translateY(' + y + 'px)';
- // 指示器从 translateY(-100%) → 0%:y 0→threshold 时,百分比 -100→0
- var pullPercent = ((y / threshold) * 100 - 100).toFixed(1);
- indicatorEl.style.transform = 'translateY(' + pullPercent + '%)';
- }
- function setSpinnerOpacity(opacity) {
- spinnerEl.style.opacity = String(opacity);
- }
- function startRefresh() {
- state.refreshing = true;
- indicatorEl.classList.add('ptr-refreshing');
- applyTransform(threshold, false);
- setSpinnerOpacity(1);
- textEl.textContent = texts.refreshing;
- onRefresh().then(function () {
- finishRefresh();
- }).catch(function () {
- finishRefresh();
- });
- }
- function finishRefresh() {
- state.refreshing = false;
- indicatorEl.classList.remove('ptr-refreshing');
- container.classList.remove('ptr-pulling');
- container.style.transform = 'translateY(0)';
- indicatorEl.style.transform = 'translateY(-100%)';
- setSpinnerOpacity(0);
- textEl.textContent = texts.pull;
- }
- function onTouchStart(e) {
- if (state.refreshing) return;
- state.startY = e.touches[0].clientY;
- state.pulling = false;
- }
- function isDocumentScrollLocked() {
- var htmlStyle = window.getComputedStyle(document.documentElement);
- return htmlStyle.overflow === 'hidden' || htmlStyle.overflowY === 'hidden';
- }
- function onTouchMove(e) {
- if (state.refreshing || isDocumentScrollLocked()) return;
- var scrollTop = getScrollTop();
- if (scrollTop > 0) return;
- var deltaY = e.touches[0].clientY - state.startY;
- if (deltaY <= 0) return;
- state.pulling = true;
- state.currentY = Math.min(deltaY * 0.5, threshold * 1.5);
- applyTransform(state.currentY, true);
- setSpinnerOpacity(Math.min(state.currentY / threshold, 1));
- textEl.textContent = state.currentY >= threshold ? texts.release : texts.pull;
- }
- function onTouchEnd() {
- if (!state.pulling || state.refreshing) return;
- state.pulling = false;
- if (state.currentY >= threshold) {
- startRefresh();
- } else {
- applyTransform(0, false);
- setSpinnerOpacity(0);
- textEl.textContent = texts.pull;
- }
- }
- this.destroy = function () {
- document.removeEventListener('touchstart', onTouchStart);
- document.removeEventListener('touchmove', onTouchMove);
- document.removeEventListener('touchend', onTouchEnd);
- if (indicatorEl && indicatorEl.parentNode) {
- indicatorEl.parentNode.removeChild(indicatorEl);
- }
- container.classList.remove('ptr-container', 'ptr-pulling');
- container.style.transform = '';
- var idx = instances.indexOf(this);
- if (idx > -1) instances.splice(idx, 1);
- };
- buildDOM();
- document.addEventListener('touchstart', onTouchStart, { passive: true });
- document.addEventListener('touchmove', onTouchMove, { passive: true });
- document.addEventListener('touchend', onTouchEnd);
- instances.push(this);
- }
- window.BEXPullRefresh = {
- init: function (container, options) {
- if (!container) throw new Error('BEXPullRefresh.init: container is required');
- return new PTRInstance(container, options);
- },
- destroyAll: function () {
- while (instances.length) {
- instances[instances.length - 1].destroy();
- }
- }
- };
- })();
|