@linear_non/stellar-kit 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/classes/FontLoader.js +1 -0
- package/classes/ImageLoader.js +116 -54
- package/classes/MasterLoader.js +1 -1
- package/events/Raf.js +15 -0
- package/kitStore.js +4 -0
- package/package.json +1 -3
- package/workers/image-loader.js +0 -10
package/classes/FontLoader.js
CHANGED
|
@@ -4,6 +4,7 @@ import { emitter, EVENTS } from "../events"
|
|
|
4
4
|
export default class FontLoader {
|
|
5
5
|
static type = "fonts"
|
|
6
6
|
static events = { PROGRESS: EVENTS.APP_FONTS_PROGRESS, LOADED: EVENTS.APP_FONTS_LOADED }
|
|
7
|
+
static getCount = (opts = {}) => opts.fonts?.length ?? 0
|
|
7
8
|
constructor({ fonts = [] }) {
|
|
8
9
|
// fonts = [{ family: "Inter", url: "/fonts/Inter.woff2" }, ...]
|
|
9
10
|
|
package/classes/ImageLoader.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ImageLoader.js
|
|
1
|
+
// classes/ImageLoader.js
|
|
2
2
|
import { emitter, EVENTS } from "../events"
|
|
3
3
|
|
|
4
4
|
export default class ImageLoader {
|
|
@@ -7,97 +7,159 @@ export default class ImageLoader {
|
|
|
7
7
|
PROGRESS: EVENTS.APP_IMAGES_PROGRESS,
|
|
8
8
|
LOADED: EVENTS.APP_IMAGES_LOADED,
|
|
9
9
|
}
|
|
10
|
+
static getCount = (opts = {}) => opts.urls?.length ?? opts.total ?? 0
|
|
10
11
|
|
|
11
|
-
constructor({ origin, fileName, extension, total, urls = [] }) {
|
|
12
|
+
constructor({ origin, fileName, extension, total, urls = [], useWorker = true, cache = null }) {
|
|
12
13
|
this.origin = origin
|
|
13
14
|
this.fileName = fileName
|
|
14
15
|
this.extension = extension
|
|
15
16
|
this.total = total
|
|
16
17
|
this.urls = urls
|
|
18
|
+
this.useWorker = useWorker
|
|
19
|
+
this.cache = cache
|
|
17
20
|
|
|
18
21
|
this.images = []
|
|
19
22
|
this.loaded = 0
|
|
20
23
|
this.isLoaded = false
|
|
24
|
+
|
|
25
|
+
this._results = new Map() // url -> HTMLImageElement
|
|
26
|
+
this._objectURLs = [] // for revoke
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
async load() {
|
|
24
|
-
const
|
|
30
|
+
const raw = this.urls.length
|
|
25
31
|
? this.urls
|
|
26
32
|
: Array.from({ length: this.total }, (_, i) => `${this.origin}/${this.fileName}${i}.${this.extension}`)
|
|
27
33
|
|
|
34
|
+
const base =
|
|
35
|
+
typeof document !== "undefined"
|
|
36
|
+
? document.baseURI
|
|
37
|
+
: typeof location !== "undefined"
|
|
38
|
+
? location.href
|
|
39
|
+
: ""
|
|
40
|
+
const urls = raw.map(u => {
|
|
41
|
+
try {
|
|
42
|
+
return new URL(u, base).href
|
|
43
|
+
} catch {
|
|
44
|
+
return u
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
console.log(urls)
|
|
49
|
+
|
|
50
|
+
// cache-aware filter
|
|
51
|
+
const toLoad = this.cache ? urls.filter(u => !this.cache.has(u)) : urls
|
|
28
52
|
this.total = urls.length
|
|
53
|
+
this.loaded = this.total - toLoad.length // count already-cached as loaded
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
55
|
+
// (optional) emit initial progress if cache pre-fills
|
|
56
|
+
if (this.loaded > 0) {
|
|
57
|
+
emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
|
|
58
|
+
loaded: this.loaded,
|
|
59
|
+
total: this.total,
|
|
60
|
+
percent: Math.round((this.loaded / this.total) * 100),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
32
63
|
|
|
64
|
+
await Promise.all(toLoad.map(url => this._loadOne(url)))
|
|
65
|
+
|
|
66
|
+
// build ordered result (use cache first, then freshly loaded)
|
|
67
|
+
this.images = urls.map(u => this.cache?.get(u) || this._results.get(u) || null)
|
|
33
68
|
this.isLoaded = true
|
|
34
69
|
emitter.emit(EVENTS.APP_IMAGES_LOADED, this.images)
|
|
35
|
-
|
|
36
70
|
return this.images
|
|
37
71
|
}
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
createWorker() {
|
|
73
|
+
_createInlineWorker() {
|
|
41
74
|
const code = `
|
|
42
75
|
self.onmessage = async (e) => {
|
|
43
|
-
const { url
|
|
76
|
+
const { url } = e.data;
|
|
44
77
|
try {
|
|
45
78
|
const res = await fetch(url);
|
|
46
79
|
const blob = await res.blob();
|
|
47
|
-
self.postMessage({ url, blob
|
|
80
|
+
self.postMessage({ url, blob });
|
|
48
81
|
} catch (err) {
|
|
49
|
-
self.postMessage({ url, error: String(err)
|
|
82
|
+
self.postMessage({ url, error: String(err) });
|
|
50
83
|
}
|
|
51
84
|
};`
|
|
52
85
|
const blob = new Blob([code], { type: "application/javascript" })
|
|
53
|
-
|
|
54
|
-
const w = new Worker(workerUrl) // classic worker (no imports)
|
|
55
|
-
// (optional) URL.revokeObjectURL(workerUrl) after creation; leaving as-is for safety
|
|
56
|
-
return w
|
|
86
|
+
return new Worker(URL.createObjectURL(blob))
|
|
57
87
|
}
|
|
58
88
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
img.src = URL.createObjectURL(blob)
|
|
71
|
-
img.onload = () => {
|
|
72
|
-
this.images[index] = img
|
|
73
|
-
emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
|
|
74
|
-
loaded: this.loaded,
|
|
75
|
-
total: this.total,
|
|
76
|
-
percent: Math.round((this.loaded / this.total) * 100),
|
|
77
|
-
})
|
|
78
|
-
resolve(img)
|
|
79
|
-
worker.terminate()
|
|
89
|
+
async _loadOne(url) {
|
|
90
|
+
// worker path
|
|
91
|
+
if (this.useWorker && typeof Worker !== "undefined") {
|
|
92
|
+
return new Promise(resolve => {
|
|
93
|
+
const w = this._createInlineWorker()
|
|
94
|
+
w.onmessage = async e => {
|
|
95
|
+
const { blob, error } = e.data
|
|
96
|
+
if (error || !blob) {
|
|
97
|
+
this._bumpProgress()
|
|
98
|
+
w.terminate()
|
|
99
|
+
return resolve(null)
|
|
80
100
|
}
|
|
81
|
-
img
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
resolve(null)
|
|
88
|
-
worker.terminate()
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
// count progress even on error
|
|
92
|
-
emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
|
|
93
|
-
loaded: this.loaded,
|
|
94
|
-
total: this.total,
|
|
95
|
-
percent: Math.round((this.loaded / this.total) * 100),
|
|
96
|
-
})
|
|
97
|
-
resolve(null)
|
|
98
|
-
worker.terminate()
|
|
101
|
+
const img = await this._blobToImage(blob)
|
|
102
|
+
this._store(url, img)
|
|
103
|
+
this._bumpProgress()
|
|
104
|
+
w.terminate()
|
|
105
|
+
resolve(img)
|
|
99
106
|
}
|
|
107
|
+
w.postMessage({ url })
|
|
100
108
|
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// fallback path (no worker)
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(url)
|
|
114
|
+
const blob = await res.blob()
|
|
115
|
+
const img = await this._blobToImage(blob)
|
|
116
|
+
this._store(url, img)
|
|
117
|
+
this._bumpProgress()
|
|
118
|
+
return img
|
|
119
|
+
} catch {
|
|
120
|
+
this._bumpProgress()
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async _blobToImage(blob) {
|
|
126
|
+
if (!blob || !blob.type?.startsWith?.("image/")) return null
|
|
127
|
+
return new Promise(resolve => {
|
|
128
|
+
const url = URL.createObjectURL(blob)
|
|
129
|
+
this._objectURLs.push(url)
|
|
130
|
+
const img = new Image()
|
|
131
|
+
img.src = url
|
|
132
|
+
img.onload = async () => {
|
|
133
|
+
if (img.decode) {
|
|
134
|
+
try {
|
|
135
|
+
await img.decode()
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
resolve(img)
|
|
139
|
+
}
|
|
140
|
+
img.onerror = () => resolve(null)
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_store(url, img) {
|
|
145
|
+
if (!img) return
|
|
146
|
+
this._results.set(url, img)
|
|
147
|
+
if (this.cache) this.cache.set(url, img)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_bumpProgress() {
|
|
151
|
+
this.loaded++
|
|
152
|
+
emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
|
|
153
|
+
loaded: this.loaded,
|
|
154
|
+
total: this.total,
|
|
155
|
+
percent: Math.round((this.loaded / this.total) * 100),
|
|
101
156
|
})
|
|
102
157
|
}
|
|
158
|
+
|
|
159
|
+
destroy() {
|
|
160
|
+
this._objectURLs.forEach(u => URL.revokeObjectURL(u))
|
|
161
|
+
this._objectURLs.length = 0
|
|
162
|
+
this.images = []
|
|
163
|
+
this._results.clear()
|
|
164
|
+
}
|
|
103
165
|
}
|
package/classes/MasterLoader.js
CHANGED
|
@@ -12,7 +12,7 @@ export default class MasterLoader {
|
|
|
12
12
|
add(loaderClass, options) {
|
|
13
13
|
const loader = new loaderClass(options)
|
|
14
14
|
this.loaders.push({ loaderClass, loader })
|
|
15
|
-
this.total += loader.total
|
|
15
|
+
this.total += loaderClass.getCount?.(options) ?? loader.total ?? 0
|
|
16
16
|
return loader
|
|
17
17
|
}
|
|
18
18
|
|
package/events/Raf.js
CHANGED
|
@@ -106,16 +106,31 @@ export default class Raf {
|
|
|
106
106
|
})
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
stop = () => {
|
|
110
|
+
gsap.ticker.remove(this.tick)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resume = () => {
|
|
114
|
+
this.scroll.current = this.scroll.target
|
|
115
|
+
this.scroll.rounded = this.scroll.target
|
|
116
|
+
gsap.ticker.add(this.tick)
|
|
117
|
+
ScrollTrigger.update()
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
on() {
|
|
110
121
|
gsap.ticker.add(this.tick)
|
|
111
122
|
emitter.on(EVENTS.APP_SCROLL, this.onScroll)
|
|
112
123
|
emitter.on(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
124
|
+
window.addEventListener("pagehide", () => this.stop)
|
|
125
|
+
window.addEventListener("pageshow", () => this.resume)
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
off() {
|
|
116
129
|
gsap.ticker.remove(this.tick)
|
|
117
130
|
emitter.off(EVENTS.APP_SCROLL, this.onScroll)
|
|
118
131
|
emitter.off(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
132
|
+
window.removeEventListener("pagehide", () => this.stop)
|
|
133
|
+
window.removeEventListener("pageshow", () => this.resume)
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
destroy() {
|
package/kitStore.js
CHANGED
|
@@ -7,6 +7,9 @@ export const sizes = {
|
|
|
7
7
|
m: 390, // mobile width
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// in case you want to "cache" your assets
|
|
11
|
+
export const assets = {}
|
|
12
|
+
|
|
10
13
|
export const flags = {
|
|
11
14
|
isFocus: false,
|
|
12
15
|
isSmooth: false,
|
|
@@ -34,4 +37,5 @@ export default {
|
|
|
34
37
|
mouse,
|
|
35
38
|
flags,
|
|
36
39
|
pageContent: null, // This should be set to the main content element
|
|
40
|
+
assets,
|
|
37
41
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linear_non/stellar-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Stellar frontend core for Non-Linear Studio projects.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
8
|
"./utils": "./utils/index.js",
|
|
9
9
|
"./classes": "./classes/index.js",
|
|
10
|
-
"./workers": "./workers/*",
|
|
11
10
|
"./events": "./events/index.js",
|
|
12
11
|
"./gsap": "./libraries/gsap/index.js"
|
|
13
12
|
},
|
|
@@ -23,7 +22,6 @@
|
|
|
23
22
|
"utils/",
|
|
24
23
|
"libraries/",
|
|
25
24
|
"libraries/gsap/",
|
|
26
|
-
"workers/",
|
|
27
25
|
"kitStore.js",
|
|
28
26
|
"index.js"
|
|
29
27
|
],
|
package/workers/image-loader.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
self.addEventListener("message", async e => {
|
|
2
|
-
const { url, index } = e.data
|
|
3
|
-
try {
|
|
4
|
-
const res = await fetch(url)
|
|
5
|
-
const blob = await res.blob()
|
|
6
|
-
self.postMessage({ url, blob, index })
|
|
7
|
-
} catch (err) {
|
|
8
|
-
console.error("Image worker error:", err)
|
|
9
|
-
}
|
|
10
|
-
})
|