scrollcat
Version:
I want to catch the best scene of my life. The browser wants to, too.
407 lines (399 loc) • 12.4 kB
JavaScript
var __accessCheck = (obj, member, msg) => {
if (!member.has(obj))
throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
__accessCheck(obj, member, "read from private field");
return getter ? getter.call(obj) : member.get(obj);
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
__accessCheck(obj, member, "write to private field");
setter ? setter.call(obj, value) : member.set(obj, value);
return value;
};
var __privateMethod = (obj, member, method) => {
__accessCheck(obj, member, "access private method");
return method;
};
// src/deferred.ts
function deferred() {
let methods;
let state = "pending";
const promise = new Promise((resolve, reject) => {
methods = {
async resolve(value) {
await value;
state = "fulfilled";
resolve(value);
},
reject(reason) {
state = "rejected";
reject(reason);
}
};
});
Object.defineProperty(promise, "state", { get: () => state });
return Object.assign(promise, methods);
}
// src/queue.ts
var _list, _limit, _top, _bottom, _destroyed, _move, move_fn, _init, init_fn;
var Queue = class {
constructor(limit = 1024) {
__privateAdd(this, _move);
__privateAdd(this, _init);
__privateAdd(this, _list, void 0);
__privateAdd(this, _limit, void 0);
__privateAdd(this, _top, 0);
__privateAdd(this, _bottom, 0);
__privateAdd(this, _destroyed, false);
__privateSet(this, _limit, limit);
__privateMethod(this, _init, init_fn).call(this);
}
async *[Symbol.asyncIterator]() {
for (; !__privateGet(this, _destroyed); ) {
yield this.pop();
}
}
pop() {
const dataDeferred = __privateGet(this, _list)[__privateGet(this, _top)];
__privateSet(this, _top, __privateMethod(this, _move, move_fn).call(this, __privateGet(this, _top)));
return dataDeferred;
}
push(data) {
__privateGet(this, _list)[__privateGet(this, _bottom)].resolve(data);
__privateSet(this, _bottom, __privateMethod(this, _move, move_fn).call(this, __privateGet(this, _bottom)));
__privateGet(this, _list)[__privateGet(this, _bottom)] = deferred();
}
refresh() {
__privateMethod(this, _init, init_fn).call(this);
}
destroy() {
if (!__privateGet(this, _destroyed)) {
__privateSet(this, _destroyed, true);
__privateGet(this, _list)[__privateGet(this, _bottom)].resolve();
}
}
};
_list = new WeakMap();
_limit = new WeakMap();
_top = new WeakMap();
_bottom = new WeakMap();
_destroyed = new WeakMap();
_move = new WeakSet();
move_fn = function(n) {
return n === __privateGet(this, _limit) ? 0 : n + 1;
};
_init = new WeakSet();
init_fn = function() {
__privateSet(this, _list, new Array(__privateGet(this, _limit) + 100));
__privateGet(this, _list)[0] = deferred();
__privateSet(this, _top, __privateSet(this, _bottom, 0));
};
// src/consts.ts
var ScrollKeys = {
" ": 1,
"Tab": 1,
"ArrowDown": 1,
"ArrowLeft": 1,
"ArrowRight": 1,
"ArrowUp": 1,
"End": 1,
"Home": 1,
"PageDown": 1,
"PageUp": 1
};
// src/utils.scroll-lock.ts
function preventDefault(e) {
e.preventDefault();
}
function preventDefaultForScrollKeys(e) {
if (ScrollKeys[e.key]) {
preventDefault(e);
return false;
}
}
var wheelOpt = { passive: false };
var wheelEvent = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel";
var lockScroll = (el) => {
el.addEventListener("DOMMouseScroll", preventDefault, false);
el.addEventListener(wheelEvent, preventDefault, wheelOpt);
el.addEventListener("touchmove", preventDefault, wheelOpt);
el.addEventListener("keydown", preventDefaultForScrollKeys, false);
};
var unlockScroll = (el) => {
el.removeEventListener("DOMMouseScroll", preventDefault, false);
el.removeEventListener(wheelEvent, preventDefault, false);
el.removeEventListener("touchmove", preventDefault, false);
el.removeEventListener("keydown", preventDefaultForScrollKeys, false);
};
// src/utils.ts
var p2nMap = { "100%": 1 };
function p2n(p) {
if (p2nMap[p] !== void 0) {
return p2nMap[p];
}
return p2nMap[p] = Number(p.slice(0, p.length - 1)) / 100;
}
// src/scene.ts
var DefaultSceneConfig = {
play: "100%",
duration: "100%"
};
var _scroller, _el, _events, _eventsMap, _prevState, _state, _cfg, _rect, _playStart, _stateChanged, stateChanged_get, _isEnter, isEnter_get, _isLeave, isLeave_get, _preUpdate, preUpdate_fn, _trigger, trigger_fn;
var Scene = class {
constructor(scroller, el, cfg) {
__privateAdd(this, _stateChanged);
__privateAdd(this, _isEnter);
__privateAdd(this, _isLeave);
__privateAdd(this, _preUpdate);
__privateAdd(this, _trigger);
__privateAdd(this, _scroller, void 0);
__privateAdd(this, _el, void 0);
__privateAdd(this, _events, new Queue());
__privateAdd(this, _eventsMap, /* @__PURE__ */ new Map());
__privateAdd(this, _prevState, void 0);
__privateAdd(this, _state, void 0);
__privateAdd(this, _cfg, void 0);
__privateAdd(this, _rect, void 0);
__privateAdd(this, _playStart, void 0);
this.isActive = true;
__privateSet(this, _scroller, scroller);
__privateSet(this, _el, el);
__privateSet(this, _cfg, cfg);
__privateMethod(this, _preUpdate, preUpdate_fn).call(this);
}
get emitter() {
return __privateGet(this, _events);
}
get scrollTop() {
return __privateGet(this, _playStart) - __privateGet(this, _rect).top;
}
get progress() {
return this.scrollTop / this.duration;
}
get state() {
return __privateGet(this, _state);
}
update() {
__privateMethod(this, _preUpdate, preUpdate_fn).call(this);
const event = {
target: __privateGet(this, _el),
scrollTop: this.scrollTop,
progress: this.progress
};
__privateMethod(this, _trigger, trigger_fn).call(this, "update", event);
if (__privateGet(this, _isEnter, isEnter_get)) {
__privateMethod(this, _trigger, trigger_fn).call(this, "enter", event);
} else if (__privateGet(this, _isLeave, isLeave_get)) {
__privateMethod(this, _trigger, trigger_fn).call(this, "leave", event);
}
}
removeSelf() {
__privateGet(this, _events).destroy();
__privateGet(this, _scroller).removeScene(this);
}
scrollTo(n, cfg) {
if (typeof n === "string") {
n = this.duration * p2n(n);
}
__privateGet(this, _scroller).scrollTo(__privateGet(this, _el).offsetTop - __privateGet(this, _playStart) + n, cfg);
}
on(eventType, cb) {
let cbs = __privateGet(this, _eventsMap).get(eventType);
if (!cbs) {
cbs = /* @__PURE__ */ new Set();
__privateGet(this, _eventsMap).set(eventType, cbs);
}
cbs.add(cb);
}
off(eventType, cb) {
const cbs = __privateGet(this, _eventsMap).get(eventType);
if (!(cbs == null ? void 0 : cbs.size)) {
return;
}
if (cb) {
cbs.delete(cb);
} else {
cbs.clear();
}
}
once(eventType, cb) {
const handler = (ev) => {
cb(ev);
this.off(eventType, handler);
};
this.on(eventType, handler);
}
};
_scroller = new WeakMap();
_el = new WeakMap();
_events = new WeakMap();
_eventsMap = new WeakMap();
_prevState = new WeakMap();
_state = new WeakMap();
_cfg = new WeakMap();
_rect = new WeakMap();
_playStart = new WeakMap();
_stateChanged = new WeakSet();
stateChanged_get = function() {
return __privateGet(this, _prevState) !== __privateGet(this, _state);
};
_isEnter = new WeakSet();
isEnter_get = function() {
if (__privateGet(this, _stateChanged, stateChanged_get)) {
const direction = __privateGet(this, _scroller).direction;
return direction === "forward" && __privateGet(this, _prevState) === "before" || direction === "reverse" && __privateGet(this, _prevState) === "after";
}
return false;
};
_isLeave = new WeakSet();
isLeave_get = function() {
return __privateGet(this, _stateChanged, stateChanged_get) && __privateGet(this, _prevState) === "during";
};
_preUpdate = new WeakSet();
preUpdate_fn = function() {
const clientHeight = __privateGet(this, _scroller).clientHeight;
const rect = __privateSet(this, _rect, __privateGet(this, _el).getBoundingClientRect());
const { top, bottom } = rect;
let playStart = __privateGet(this, _cfg).play;
if (typeof playStart === "string") {
playStart = clientHeight * p2n(playStart);
}
__privateSet(this, _playStart, playStart);
let duration = __privateGet(this, _cfg).duration;
if (typeof duration === "string") {
duration = (bottom - top) * p2n(duration);
}
this.duration = duration;
__privateSet(this, _prevState, __privateGet(this, _state));
const newState = top <= playStart && top + duration >= playStart ? "during" : top < playStart ? "after" : "before";
if (!__privateGet(this, _state)) {
__privateSet(this, _prevState, newState);
}
__privateSet(this, _state, newState);
};
_trigger = new WeakSet();
trigger_fn = function(type, event) {
__privateGet(this, _events).push([type, event]);
const cbs = __privateGet(this, _eventsMap).get(type);
if (cbs) {
for (const cb of cbs) {
cb(event);
}
}
};
// src/scroller.ts
var doc = self.document;
var _el2, _scenes, _prevScrollTop, _locker, _update, update_fn;
var Scroller = class {
constructor(el) {
__privateAdd(this, _update);
__privateAdd(this, _el2, void 0);
__privateAdd(this, _scenes, /* @__PURE__ */ new Set());
__privateAdd(this, _prevScrollTop, void 0);
__privateAdd(this, _locker, void 0);
__privateSet(this, _el2, el);
doc.addEventListener("scroll", __privateMethod(this, _update, update_fn).bind(this));
this.destroy = () => {
doc.removeEventListener("scroll", __privateMethod(this, _update, update_fn).bind(this));
for (const scene of __privateGet(this, _scenes)) {
this.removeScene(scene);
}
this.unlock();
};
}
get clientHeight() {
return self.innerHeight || doc.documentElement.clientHeight;
}
get locked() {
return !!__privateGet(this, _locker);
}
get el() {
return __privateGet(this, _el2) || doc.body;
}
addScene(el, cfg) {
const scene = new Scene(this, el, cfg);
__privateGet(this, _scenes).add(scene);
return scene;
}
addSceneWithDefault(el) {
return this.addScene(el, DefaultSceneConfig);
}
removeScene(scene) {
if (__privateGet(this, _scenes).has(scene)) {
__privateGet(this, _scenes).delete(scene);
scene.removeSelf();
}
}
scrollTo(n, cfg = {}) {
if (typeof n === "string") {
n = p2n(n);
}
if (__privateGet(this, _el2) === void 0) {
self.scrollTo({ top: n, ...cfg });
}
__privateMethod(this, _update, update_fn).call(this);
}
lock(duration) {
if (__privateGet(this, _locker)) {
this.unlock();
}
const future = deferred();
const currentScrollTop = this.scrollTop;
lockScroll(this.el);
let ticking = false;
__privateSet(this, _locker, [
setInterval(() => {
if (!ticking) {
self.requestAnimationFrame(() => {
this.scrollTo(currentScrollTop);
ticking = false;
});
ticking = true;
}
}, 0),
future
]);
if (duration) {
setTimeout(this.unlock.bind(this), duration);
}
return future;
}
unlock() {
unlockScroll(this.el);
if (__privateGet(this, _locker)) {
clearInterval(__privateGet(this, _locker)[0]);
__privateGet(this, _locker)[1].resolve();
__privateSet(this, _locker, void 0);
}
}
};
_el2 = new WeakMap();
_scenes = new WeakMap();
_prevScrollTop = new WeakMap();
_locker = new WeakMap();
_update = new WeakSet();
update_fn = function() {
__privateSet(this, _prevScrollTop, this.scrollTop);
this.scrollTop = self.scrollY;
if (!__privateGet(this, _prevScrollTop)) {
__privateSet(this, _prevScrollTop, this.scrollTop);
}
const delta = this.scrollTop - __privateGet(this, _prevScrollTop);
this.direction = delta > 0 ? "forward" : delta < 0 ? "reverse" : "paused";
for (const scene of __privateGet(this, _scenes)) {
if (scene.isActive) {
scene.update();
}
}
};
export {
DefaultSceneConfig,
Scene,
Scroller
};