@linear_non/stellar-libs 1.2.5 → 1.2.7

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.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Reusable JavaScript libraries for Non-Linear Studio projects.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,74 @@
1
+ import { lerp } from "@linear_non/stellar-kit/utils"
2
+ import { gsap } from "@linear_non/stellar-kit/gsap"
3
+
4
+ export default class CursorTracker {
5
+ constructor({ container, el, ease = 0.12, isCentered = true }) {
6
+ this.container = container
7
+ this.el = el
8
+ this.ease = ease
9
+ this.isCentered = isCentered
10
+
11
+ this.pos = { x: 0, y: 0 }
12
+ this.target = { x: 0, y: 0 }
13
+ this.isHovering = false
14
+
15
+ this.init()
16
+ }
17
+
18
+ setup() {
19
+ if (this.isCentered) {
20
+ gsap.set(this.el, { xPercent: -50, yPercent: -50 })
21
+ }
22
+ }
23
+
24
+ // GLOBAL TICK
25
+ tick({ mouse }) {
26
+ if (!this.isHovering) return
27
+
28
+ this.target.x = mouse.x
29
+ this.target.y = mouse.y
30
+
31
+ this.pos.x = lerp(this.pos.x, this.target.x, this.ease)
32
+ this.pos.y = lerp(this.pos.y, this.target.y, this.ease)
33
+
34
+ gsap.set(this.el, {
35
+ x: this.pos.x,
36
+ y: this.pos.y,
37
+ })
38
+ }
39
+
40
+ hoverIn = () => {
41
+ this.isHovering = true
42
+ gsap.to(this.el, { autoAlpha: 1, duration: 0.2, overwrite: true })
43
+ }
44
+
45
+ hoverOut = () => {
46
+ this.isHovering = false
47
+ gsap.to(this.el, { autoAlpha: 0, duration: 0.2, overwrite: true })
48
+ }
49
+
50
+ on() {
51
+ if (!this.container) return
52
+
53
+ this.container.addEventListener("mouseenter", this.hoverIn)
54
+ this.container.addEventListener("mouseleave", this.hoverOut)
55
+ }
56
+
57
+ off() {
58
+ if (!this.container) return
59
+
60
+ this.container.removeEventListener("mouseenter", this.hoverIn)
61
+ this.container.removeEventListener("mouseleave", this.hoverOut)
62
+ }
63
+
64
+ destroy() {
65
+ this.off()
66
+ this.container = null
67
+ this.el = null
68
+ }
69
+
70
+ init() {
71
+ this.setup()
72
+ this.on()
73
+ }
74
+ }
@@ -0,0 +1,91 @@
1
+ import { gsap } from "@linear_non/stellar-kit/gsap"
2
+
3
+ export default class Marquee {
4
+ constructor({ container, el, speed = 50, gap = "1.6rem" }) {
5
+ this.container = container
6
+ this.el = el
7
+ this.speed = speed
8
+ this.gap = gap
9
+ this.tween = null
10
+ this.init()
11
+ }
12
+
13
+ setup() {
14
+ if (this.tween) this.tween.kill()
15
+
16
+ gsap.set(this.container, {
17
+ overflow: "hidden",
18
+ display: "flex",
19
+ alignItems: "flex-start",
20
+ })
21
+
22
+ gsap.set(this.el, {
23
+ display: "flex",
24
+ whiteSpace: "nowrap",
25
+ width: "max-content",
26
+ x: 0,
27
+ gap: this.gap,
28
+ willChange: "transform",
29
+ })
30
+
31
+ const source = this.el.children[0]
32
+ if (!source) return
33
+
34
+ // Use bounding rect for more accurate (subpixel) width
35
+ const itemWidth = source.getBoundingClientRect().width
36
+ if (!itemWidth) return
37
+
38
+ // Convert computed gap to px
39
+ const gapPx = parseFloat(getComputedStyle(this.el).gap) || 0
40
+
41
+ // One "step" includes the gap after the item
42
+ const stepWidth = itemWidth + gapPx
43
+
44
+ // Copies needed to cover viewport (+ a little buffer)
45
+ const copies = Math.ceil(window.innerWidth / stepWidth) + 2
46
+
47
+ while (this.el.children.length < copies * 2) {
48
+ const clone = source.cloneNode(true)
49
+ clone.setAttribute("aria-hidden", "true")
50
+ this.el.appendChild(clone)
51
+ }
52
+
53
+ // Loop distance must include gaps too
54
+ const setWidth = copies * stepWidth
55
+ const duration = setWidth / this.speed
56
+
57
+ const wrapX = gsap.utils.wrap(-setWidth, 0)
58
+
59
+ this.tween = gsap.to(this.el, {
60
+ x: `-=${setWidth}`,
61
+ duration,
62
+ ease: "none",
63
+ repeat: -1,
64
+ modifiers: {
65
+ // GSAP gives strings like "-123.45px"
66
+ x: v => `${wrapX(parseFloat(v))}px`,
67
+ },
68
+ })
69
+ }
70
+
71
+ on() {
72
+ this._onResize = () => this.setup()
73
+ window.addEventListener("resize", this._onResize)
74
+ }
75
+
76
+ off() {
77
+ if (this._onResize) window.removeEventListener("resize", this._onResize)
78
+ this._onResize = null
79
+ }
80
+
81
+ destroy() {
82
+ this.off()
83
+ if (this.tween) this.tween.kill()
84
+ this.tween = null
85
+ }
86
+
87
+ init() {
88
+ this.setup()
89
+ this.on()
90
+ }
91
+ }
@@ -5,13 +5,14 @@ import { kitStore } from "@linear_non/stellar-kit"
5
5
 
6
6
  export default class Sticky {
7
7
  constructor(obj) {
8
- const { el, sticky, container, isSmooth, onUpdateCallback } = obj
8
+ const { el, sticky, container, frame, isSmooth, onUpdateCallback } = obj
9
9
 
10
10
  this.el = el
11
11
  this.sticky = sticky
12
12
  this.isSticky = false
13
13
  this.isSmooth = isSmooth
14
14
  this.container = container
15
+ this.frame = frame || null
15
16
  this.progress = 0
16
17
  this.total = 0
17
18
  this.positionX = 0
@@ -97,13 +98,34 @@ export default class Sticky {
97
98
  resize() {
98
99
  if (!this.sticky) return
99
100
 
100
- // Get the viewport height dynamically instead of using store
101
101
  const vh = window.innerHeight
102
- const vw = window.innerWidth
103
- let stickyRect = bounds(this.el)
104
- let containerRect = this.container ? bounds(this.container) : null
105
- this.config.totalWidth = containerRect ? this.snap(containerRect.width - vw) : 0
102
+
103
+ const stickyRect = bounds(this.el)
104
+
105
+ // HEIGHT (same as you have)
106
106
  this.config.totalHeight = this.snap(stickyRect.height - vh)
107
+
108
+ // WIDTH (fix)
109
+ if (!this.container) {
110
+ this.config.totalWidth = 0
111
+ return
112
+ }
113
+
114
+ const frameEl = this.frame || this.container.parentElement
115
+ const frameRect = frameEl ? bounds(frameEl) : { width: window.innerWidth }
116
+
117
+ let padL = 0
118
+ let padR = 0
119
+ if (frameEl) {
120
+ const cs = getComputedStyle(frameEl)
121
+ padL = parseFloat(cs.paddingLeft) || 0
122
+ padR = parseFloat(cs.paddingRight) || 0
123
+ }
124
+
125
+ const visibleContentWidth = Math.max(0, frameRect.width - padL - padR)
126
+ const totalContentWidth = this.container.scrollWidth || bounds(this.container).width
127
+
128
+ this.config.totalWidth = this.snap(Math.max(0, totalContentWidth - visibleContentWidth))
107
129
  }
108
130
 
109
131
  destroy() {
package/src/index.js CHANGED
@@ -3,5 +3,7 @@ import Smooth from "./Smooth"
3
3
  import SplitonScroll from "./SplitOnScroll"
4
4
  import Noise from "./Noise"
5
5
  import SpritePlayer from "./SpritePlayer"
6
+ import CursorTracker from "./CursorTracker"
7
+ import Marquee from "./Marquee"
6
8
 
7
- export { Sticky, Smooth, SplitonScroll, Noise, SpritePlayer }
9
+ export { Sticky, Smooth, SplitonScroll, Noise, SpritePlayer, CursorTracker, Marquee }