@linear_non/stellar-libs 1.0.4 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-libs",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Reusable JavaScript libraries for Non-Linear Studio projects.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,138 @@
1
+ // stellar-libs/effects/Noise.js
2
+ import { kitStore } from "@linear_non/stellar-kit"
3
+ import { emitter, EVENTS } from "@linear_non/stellar-kit/events"
4
+ import { qs } from "@linear_non/stellar-kit/utils"
5
+
6
+ export default class Noise {
7
+ constructor(opts = {}) {
8
+ const {
9
+ target = ".-noise",
10
+ density = 0.75,
11
+ color = 0xf3f2feff,
12
+ opacity = 0.25,
13
+ fps = 24,
14
+ tileSize = 160,
15
+ useDPR = true,
16
+ composite = "source-over",
17
+ } = opts
18
+
19
+ this.density = Math.max(0, Math.min(1, density))
20
+ this.color = color >>> 0
21
+ this.opacity = Math.max(0, Math.min(1, opacity))
22
+ this.fps = Math.max(1, fps)
23
+ this.tileSize = Math.max(8, tileSize)
24
+ this.useDPR = useDPR
25
+ this.composite = composite
26
+
27
+ // Resolve canvas
28
+ this.canvas = typeof target === "string" ? qs(target) : target
29
+
30
+ if (!this.canvas) {
31
+ // Create one if not found; useful when dropping in quickly
32
+ this.canvas = document.createElement("canvas")
33
+ this.canvas.className = "-noise"
34
+ Object.assign(this.canvas.style, {
35
+ position: "fixed",
36
+ inset: 0,
37
+ width: "100vw",
38
+ height: "100vh",
39
+ pointerEvents: "none",
40
+ zIndex: 1,
41
+ })
42
+ document.body.appendChild(this.canvas)
43
+ }
44
+
45
+ this.ctx = this.canvas.getContext("2d", { alpha: true })
46
+ this.tile = document.createElement("canvas")
47
+ this.tileCtx = this.tile.getContext("2d", { alpha: true })
48
+
49
+ this._msPerFrame = 1000 / this.fps
50
+ this._last = 0
51
+ this._running = false
52
+
53
+ this._boundTick = this.tick.bind(this)
54
+ this._boundResize = this.resize.bind(this)
55
+
56
+ this.resize()
57
+ this.start()
58
+ }
59
+
60
+ start() {
61
+ if (this._running) return
62
+ this._running = true
63
+ emitter.on(EVENTS.APP_TICK, this.tick)
64
+ emitter.on(EVENTS.APP_RESIZE, this.resize)
65
+ }
66
+
67
+ stop() {
68
+ if (!this._running) return
69
+ this._running = false
70
+ emitter.off(EVENTS.APP_TICK, this.tick)
71
+ emitter.off(EVENTS.APP_RESIZE, this.resize)
72
+ }
73
+
74
+ destroy() {
75
+ this.stop()
76
+ // Optional: clear canvas
77
+ this.ctx?.clearRect(0, 0, this.canvas.width, this.canvas.height)
78
+ }
79
+
80
+ resize() {
81
+ const { sizes } = kitStore || {}
82
+ const vw = sizes?.vw || window.innerWidth
83
+ const vh = sizes?.vh || window.innerHeight
84
+ const dpr = this.useDPR ? sizes?.dpr || window.devicePixelRatio || 1 : 1
85
+
86
+ // Backing store
87
+ this.canvas.width = Math.max(1, Math.floor(vw * dpr))
88
+ this.canvas.height = Math.max(1, Math.floor(vh * dpr))
89
+
90
+ // CSS size
91
+ this.canvas.style.width = `${vw}px`
92
+ this.canvas.style.height = `${vh}px`
93
+
94
+ // Tile is DPR-agnostic; we upscale it anyway
95
+ this.tile.width = this.tileSize
96
+ this.tile.height = this.tileSize
97
+ }
98
+
99
+ tick = () => {
100
+ if (!this._running) return
101
+ const now = performance.now()
102
+ if (now - this._last < this._msPerFrame) return
103
+ this._last = now
104
+ this.drawTile()
105
+ this.blit()
106
+ }
107
+
108
+ drawTile() {
109
+ const { tileCtx } = this
110
+ const w = this.tile.width
111
+ const h = this.tile.height
112
+
113
+ const iData = tileCtx.createImageData(w, h)
114
+ const buf32 = new Uint32Array(iData.data.buffer)
115
+ const len = buf32.length
116
+ const color = this.color
117
+
118
+ // Fill sparse pixels for grainy look
119
+ // (leaving others transparent keeps the “salt & pepper”)
120
+ const threshold = this.density
121
+ for (let i = 0; i < len; i++) {
122
+ if (Math.random() < threshold) buf32[i] = color
123
+ }
124
+ tileCtx.putImageData(iData, 0, 0)
125
+ }
126
+
127
+ blit() {
128
+ const { ctx, canvas, tile } = this
129
+ if (!ctx) return
130
+ ctx.save()
131
+ ctx.globalCompositeOperation = this.composite
132
+ ctx.globalAlpha = this.opacity // 👈 softens the whole layer
133
+ ctx.imageSmoothingEnabled = false
134
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
135
+ ctx.drawImage(tile, 0, 0, tile.width, tile.height, 0, 0, canvas.width, canvas.height)
136
+ ctx.restore()
137
+ }
138
+ }
@@ -13,6 +13,7 @@ export default class Smooth {
13
13
  this.threshold = 100
14
14
  this.sections = null
15
15
  this.scrollbar = null
16
+ this.dpr = Math.max(1, Math.round(window.devicePixelRatio || 1))
16
17
  this.init()
17
18
  }
18
19
 
@@ -81,7 +82,9 @@ export default class Smooth {
81
82
  }
82
83
 
83
84
  getTransform(transform) {
84
- return `translate3d(0, ${-transform}px, 0)`
85
+ let y = -transform
86
+ y = Math.round(y * this.dpr) / this.dpr
87
+ return `translate3d(0, ${y}px, 0)`
85
88
  }
86
89
 
87
90
  getVars(el, speed) {
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import Sticky from "./Sticky"
2
2
  import Smooth from "./Smooth"
3
3
  import SplitonScroll from "./SplitOnScroll"
4
+ import Noise from "./Noise"
4
5
 
5
- export { Sticky, Smooth, SplitonScroll }
6
+ export { Sticky, Smooth, SplitonScroll, Noise }