@linear_non/stellar-libs 1.0.8 → 1.0.10

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A collection of lightweight, reusable frontend UI behavior modules — built to work seamlessly with [`stellar-kit`](https://www.npmjs.com/package/@linear_non/stellar-kit) and the [Stellar](https://github.com/nonlinearstudio/stellar) frontend system. Each library is self-contained, configurable, and designed to be dropped into modular websites. Also is a project that is constantly growing and we are adding new updates all the time.
4
4
 
5
- ## ✨ Avalible Libs
5
+ ## ✨ Available Libs
6
6
 
7
7
  - Designed for plug-and-play
8
8
  - **Sticky**: Basic sticky logic for DOM elements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-libs",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Reusable JavaScript libraries for Non-Linear Studio projects.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -14,14 +14,18 @@ export default class SpritePlayer {
14
14
  total = 0,
15
15
  progress = 0,
16
16
  fileExtension = "png",
17
- cache = null, // optional Map for global cache
18
- persistent = false, // true if using shared cache (don’t revoke)
17
+ cache = null,
18
+ persistent = false,
19
+ alpha_path = null, // { mobile, desktop }
20
+ fileAlpha = null,
21
+ useAlpha = false,
19
22
  }) {
20
23
  const rect = bounds(container)
21
- this.progress = progress
24
+ this.progress = gsap.utils.clamp(0, 1, progress)
22
25
  this.persistent = persistent
26
+ this.isAlpha = !!useAlpha
23
27
 
24
- this.dom = { container, reference: container, images: [] }
28
+ this.dom = { container, reference: container, images: [], alphaImages: [] }
25
29
  this.canvas = {
26
30
  el: canvas,
27
31
  ctx: canvas.getContext("2d"),
@@ -37,6 +41,8 @@ export default class SpritePlayer {
37
41
  total,
38
42
  file_extension: fileExtension,
39
43
  cache,
44
+ alpha_path,
45
+ fileAlpha,
40
46
  }
41
47
 
42
48
  this.state = { loaded: false }
@@ -45,27 +51,44 @@ export default class SpritePlayer {
45
51
  }
46
52
 
47
53
  async loadImages() {
48
- const { mobile_path, desktop_path, total, file_name, file_extension, cache } = this.settings
49
- const origin = sniffer.isDesktop ? desktop_path : mobile_path
54
+ const { mobile_path, desktop_path, alpha_path, file_name, fileAlpha, total, file_extension, cache } =
55
+ this.settings
56
+
57
+ const originBase = sniffer.isDesktop ? desktop_path : mobile_path || desktop_path
58
+ const originAlpha = alpha_path
59
+ ? sniffer.isDesktop
60
+ ? alpha_path.desktop || alpha_path.mobile
61
+ : alpha_path.mobile || alpha_path.desktop
62
+ : null
63
+
64
+ const make = (origin, name) =>
65
+ new ImageLoader({
66
+ origin,
67
+ fileName: name,
68
+ extension: file_extension,
69
+ total,
70
+ cache,
71
+ useWorker: true,
72
+ }).load()
50
73
 
51
- const loader = new ImageLoader({
52
- origin,
53
- fileName: file_name,
54
- extension: file_extension,
55
- total,
56
- cache, // pass cache Map if you have one
57
- useWorker: true,
58
- })
59
-
60
- // (optional) forward progress
61
74
  const onProgress = e => emitter.emit("sprite:progress", { instance: this, ...e })
62
75
  emitter.on(ImageLoader.events.PROGRESS, onProgress)
63
76
 
64
- const images = await loader.load() // ✅ get images from promise
77
+ const [images, alphaImages] = await Promise.all([
78
+ make(originBase, file_name),
79
+ originAlpha && fileAlpha ? make(originAlpha, fileAlpha) : Promise.resolve([]),
80
+ ])
65
81
 
66
82
  emitter.off?.(ImageLoader.events.PROGRESS, onProgress)
67
83
 
68
84
  this.dom.images = images
85
+ this.dom.alphaImages = alphaImages
86
+
87
+ if (this.isAlpha && this.dom.alphaImages.length) {
88
+ this.dom.alphaImages = this.dom.alphaImages.map(img => this.toAlphaFromLuma(img))
89
+ }
90
+ if (this.isAlpha && !this.dom.alphaImages.length) this.isAlpha = false
91
+
69
92
  this.frameCount = images.length
70
93
  this.state.loaded = true
71
94
  emitter.emit("sprite:loaded", this)
@@ -89,9 +112,31 @@ export default class SpritePlayer {
89
112
  Object.assign(this.canvas, { width: rect.width, height: rect.height, rect, area, offset })
90
113
  }
91
114
 
115
+ // grayscale -> alpha (returns a canvas)
116
+ toAlphaFromLuma(img) {
117
+ const w = img.naturalWidth || img.width,
118
+ h = img.naturalHeight || img.height
119
+ const c = document.createElement("canvas")
120
+ c.width = w
121
+ c.height = h
122
+ const x = c.getContext("2d")
123
+ x.drawImage(img, 0, 0, w, h)
124
+ const d = x.getImageData(0, 0, w, h)
125
+ const a = d.data
126
+ for (let i = 0; i < a.length; i += 4) {
127
+ const lum = a[i] * 0.2126 + a[i + 1] * 0.7152 + a[i + 2] * 0.0722
128
+ a[i] = 0
129
+ a[i + 1] = 0
130
+ a[i + 2] = 0
131
+ a[i + 3] = lum
132
+ }
133
+ x.putImageData(d, 0, 0)
134
+ return c
135
+ }
136
+
92
137
  drawImageCover(ctx, img, area, offset) {
93
- const iw = img.naturalWidth,
94
- ih = img.naturalHeight
138
+ const iw = img.naturalWidth || img.width
139
+ const ih = img.naturalHeight || img.height
95
140
  const cw = area.width,
96
141
  ch = area.height
97
142
  const ir = iw / ih,
@@ -116,14 +161,24 @@ export default class SpritePlayer {
116
161
  this.scroll = current
117
162
 
118
163
  const { ctx, width, height, area, offset } = this.canvas
119
- const { images } = this.dom
164
+ const { images, alphaImages } = this.dom
120
165
  const total = this.frameCount || images.length || 1
121
- const val = gsap.utils.clamp(0, total - 1, this.progress * total)
122
- const index = Math.floor(val)
123
- const img = images[index]
166
+ const index = Math.min(total - 1, Math.round(this.progress * (total - 1)))
167
+ const base = images[index]
168
+ const hasMasks = this.isAlpha && alphaImages.length === images.length
169
+ const mask = hasMasks ? alphaImages[index] : null
124
170
 
125
171
  ctx.clearRect(0, 0, width, height)
126
- if (img) this.drawImageCover(ctx, img, area, offset)
172
+ if (!base) return
173
+
174
+ this.drawImageCover(ctx, base, area, offset)
175
+
176
+ if (mask) {
177
+ ctx.save()
178
+ ctx.globalCompositeOperation = "destination-in"
179
+ this.drawImageCover(ctx, mask, area, offset)
180
+ ctx.restore()
181
+ }
127
182
  }
128
183
 
129
184
  resize = () => this.configureCanvas()
@@ -137,11 +192,30 @@ export default class SpritePlayer {
137
192
  emitter.off(EVENTS.APP_RESIZE, this.resize)
138
193
  }
139
194
 
195
+ setProgress(p) {
196
+ this.progress = gsap.utils.clamp(0, 1, p)
197
+ }
198
+ show() {
199
+ this.isVisible = true
200
+ }
201
+ hide() {
202
+ this.isVisible = false
203
+ const { ctx, width, height } = this.canvas
204
+ ctx.clearRect(0, 0, width, height)
205
+ }
206
+ enableAlpha(v = true) {
207
+ this.isAlpha = !!v
208
+ }
209
+
140
210
  destroy() {
141
211
  this.off()
142
- // Only revoke if not using shared cache
143
- if (!this.persistent) this.dom.images.forEach(img => URL.revokeObjectURL(img?.src))
212
+ if (!this.persistent) {
213
+ const revoke = img => img?.src?.startsWith("blob:") && URL.revokeObjectURL(img.src)
214
+ this.dom.images.forEach(revoke)
215
+ this.dom.alphaImages.forEach(revoke)
216
+ }
144
217
  this.dom.images = []
218
+ this.dom.alphaImages = []
145
219
  }
146
220
 
147
221
  async init() {