Skip to content

Commit 7f31c8c

Browse files
committed
2 parents c6ce954 + f2b468c commit 7f31c8c

3 files changed

Lines changed: 119 additions & 70 deletions

File tree

src/app/home/home.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,17 +249,19 @@ drag-scroll::-webkit-scrollbar {
249249
-webkit-user-select: none;
250250
-webkit-user-drag: none;
251251
user-drag: none;
252-
pointer-events: none;
252+
pointer-events: auto;
253+
cursor: pointer;
254+
pointer-events: all;
253255
}
254256

255-
/* Prevent profile image dragging */
256257
.horizontal-scroll-container img,
257258
.horizontal-scroll-container app-event-header img {
258259
-webkit-user-drag: none;
259260
user-drag: none;
260261
pointer-events: none !important;
261262
-webkit-user-select: none;
262263
user-select: none;
264+
touch-action: none;
263265
}
264266

265267
.horizontal-scroll-container app-event-header:hover {
@@ -317,6 +319,7 @@ drag-scroll::-webkit-scrollbar {
317319
height: 64px;
318320
margin-left: 4px;
319321
pointer-events: auto;
322+
pointer-events: all;
320323
}
321324

322325
.profile-line-more-button {
@@ -328,4 +331,5 @@ drag-scroll::-webkit-scrollbar {
328331
align-items: center;
329332
justify-content: center;
330333
pointer-events: auto;
334+
pointer-events: all;
331335
}

src/app/home/home.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
[displayName]="false"
1111
[displayContent]="false"
1212
[profile]="profile"
13-
draggable="false">
13+
[routerLink]="['/p', profile.pubkey]"
14+
draggable="false"
15+
ondragstart="return false;">
1416
</app-event-header>
1517
}
1618
<div class="profile-line-more-wrapper">

src/app/shared/directives/drag-scroll.directive.ts

Lines changed: 110 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,93 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
1515
private startTime = signal(0);
1616
private moveDistance = signal(0);
1717

18-
// Momentum scrolling variables
1918
private velocity = signal(0);
2019
private timestamp = signal(0);
2120
private frame = signal(0);
2221
private amplitude = signal(0);
2322
private target = signal(0);
24-
private timeConstant = 325; // ms - adjust for scroll momentum feel
23+
private timeConstant = 500;
2524
private animationActive = false;
2625

27-
// Add this new property to track if we should suppress clicks
26+
private velocityFactor = 0.5;
27+
private momentumMultiplier = 8;
28+
2829
private suppressClick = signal(false);
29-
private clickThreshold = 3; // Lower threshold to better differentiate clicks from drags
30+
private clickThreshold = 3;
31+
32+
33+
private startPosition = signal(0);
34+
private clickStartTime = signal(0);
3035

3136
ngAfterViewInit(): void {
32-
// Add passive event listener for smoother scrolling
3337
this.ngZone.runOutsideAngular(() => {
3438
this.element.nativeElement.addEventListener('wheel', (e: WheelEvent) => {
3539
e.preventDefault();
3640
const scrollAmount = e.deltaY || e.deltaX;
3741
this.element.nativeElement.scrollLeft += scrollAmount;
3842
}, { passive: false });
3943

40-
// Prevent default drag behavior on all images within the container
41-
const images = this.element.nativeElement.querySelectorAll('img');
42-
images.forEach((img: HTMLImageElement) => {
43-
img.setAttribute('draggable', 'false');
44-
img.style.pointerEvents = 'none';
44+
45+
const observer = new MutationObserver((mutations) => {
46+
this.preventElementDrag();
4547
});
4648

47-
// Also prevent drag on app-event-header components
48-
const headers = this.element.nativeElement.querySelectorAll('app-event-header');
49-
headers.forEach((header: HTMLElement) => {
50-
header.addEventListener('mousedown', (e: MouseEvent) => {
51-
// Only prevent default for left mouse button when we're intending to drag
52-
if (e.button === 0) {
53-
e.preventDefault();
54-
}
55-
});
56-
57-
// Prevent drag start
58-
header.addEventListener('dragstart', (e: DragEvent) => {
59-
e.preventDefault();
60-
});
49+
observer.observe(this.element.nativeElement, {
50+
childList: true,
51+
subtree: true
6152
});
53+
54+
55+
this.preventElementDrag();
6256
});
6357
}
6458

59+
60+
private preventElementDrag(): void {
61+
62+
const images = this.element.nativeElement.querySelectorAll('img');
63+
images.forEach((img: HTMLImageElement) => {
64+
img.setAttribute('draggable', 'false');
65+
img.style.pointerEvents = 'none';
66+
});
67+
68+
69+
const headers = this.element.nativeElement.querySelectorAll('app-event-header');
70+
headers.forEach((header: HTMLElement) => {
71+
header.setAttribute('draggable', 'false');
72+
73+
74+
header.removeEventListener('dragstart', this.preventDragHandler);
75+
header.addEventListener('dragstart', this.preventDragHandler);
76+
});
77+
}
78+
79+
80+
private preventDragHandler = (e: DragEvent) => {
81+
e.preventDefault();
82+
e.stopPropagation();
83+
return false;
84+
};
85+
6586
ngOnDestroy(): void {
6687
this.cancelAnimation();
88+
89+
90+
try {
91+
const headers = this.element.nativeElement.querySelectorAll('app-event-header');
92+
headers.forEach((header: HTMLElement) => {
93+
header.removeEventListener('dragstart', this.preventDragHandler);
94+
});
95+
} catch (error) {
96+
97+
}
6798
}
6899

69100
@HostListener('mousedown', ['$event'])
70101
onMouseDown(event: MouseEvent): void {
71102
this.cancelAnimation();
72103

104+
73105
this.isDown.set(true);
74106
this.startTime.set(Date.now());
75107
this.moveDistance.set(0);
@@ -78,12 +110,27 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
78110
this.timestamp.set(Date.now());
79111
this.velocity.set(0);
80112

113+
114+
this.startPosition.set(event.pageX);
115+
this.clickStartTime.set(Date.now());
116+
81117
this.element.nativeElement.classList.add('active');
82118
this.startX.set(event.pageX);
83119
this.scrollLeft.set(this.element.nativeElement.scrollLeft);
84120

85-
// Prevent default behavior for drag scrolling
86-
event.preventDefault();
121+
122+
123+
const target = event.target as HTMLElement;
124+
const isClickable =
125+
target.tagName === 'BUTTON' ||
126+
target.tagName === 'A' ||
127+
target.closest('button') ||
128+
target.closest('a') ||
129+
target.closest('mat-icon');
130+
131+
if (!isClickable) {
132+
event.preventDefault();
133+
}
87134
}
88135

89136
@HostListener('mouseleave')
@@ -93,10 +140,7 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
93140
this.isDown.set(false);
94141
this.element.nativeElement.classList.remove('active');
95142

96-
// Start momentum scrolling if we were dragging
97-
if (this.isDragging()) {
98-
this.autoScroll();
99-
}
143+
this.cancelAnimation();
100144
}
101145

102146
@HostListener('mouseup', ['$event'])
@@ -106,27 +150,37 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
106150
this.isDown.set(false);
107151
this.element.nativeElement.classList.remove('active');
108152

109-
// If just a click (no significant drag) allow event to propagate
110-
if (!this.isDragging() && this.moveDistance() < this.clickThreshold) {
153+
154+
const distance = Math.abs(event.pageX - this.startPosition());
155+
const timeElapsed = Date.now() - this.clickStartTime();
156+
157+
158+
const isClick = distance < 5 && timeElapsed < 300;
159+
160+
161+
if (isClick) {
162+
this.suppressClick.set(false);
111163
return;
112164
}
113165

114-
// If we've dragged significantly, prevent the click event
166+
115167
if (this.isDragging() || this.moveDistance() >= this.clickThreshold) {
116-
this.suppressClick.set(true);
117168

118-
// Start momentum scrolling
119-
this.autoScroll();
169+
if (distance > 10) {
170+
this.suppressClick.set(true);
171+
}
172+
173+
this.cancelAnimation();
120174
}
121175
}
122176

123-
// Add a click event handler to prevent clicks after dragging
124177
@HostListener('click', ['$event'])
125178
onClick(event: MouseEvent): void {
179+
126180
if (this.suppressClick()) {
127181
event.stopPropagation();
128182
event.preventDefault();
129-
this.suppressClick.set(false); // Reset for next interaction
183+
this.suppressClick.set(false);
130184
}
131185
}
132186

@@ -137,38 +191,31 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
137191
const x = event.pageX;
138192
const delta = x - this.startX();
139193

140-
// If moved more than threshold, consider it a drag
194+
141195
if (Math.abs(delta) > this.clickThreshold && !this.isDragging()) {
142196
this.isDragging.set(true);
143-
event.preventDefault(); // Prevent text selection when dragging
144197
}
145198

146-
// Calculate velocity for momentum scrolling
147199
const now = Date.now();
148200
const elapsed = now - this.timestamp();
149201

150-
// Update timestamp
151202
this.timestamp.set(now);
152203

153-
// Calculate movement
154-
const currentScrollLeft = this.scrollLeft() - delta;
204+
const damping = 0.85;
205+
const currentScrollLeft = this.scrollLeft() - (delta * damping);
155206
this.element.nativeElement.scrollLeft = currentScrollLeft;
156207

157-
// Update values for next move
158208
this.startX.set(x);
159209
this.scrollLeft.set(this.element.nativeElement.scrollLeft);
160210

161-
// Calculate velocity (pixels/ms)
162211
if (elapsed > 0) {
163-
const v = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocity();
212+
const v = 0.6 * (1000 * delta / (1 + elapsed)) + 0.4 * this.velocity();
164213
this.velocity.set(v);
165214
}
166215

167-
// Track total distance moved for distinguishing clicks from drags
168216
this.moveDistance.set(this.moveDistance() + Math.abs(delta));
169217
}
170218

171-
// Touch support with inertial scrolling
172219
@HostListener('touchstart', ['$event'])
173220
onTouchStart(event: TouchEvent): void {
174221
if (event.touches.length !== 1) return;
@@ -195,13 +242,11 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
195242
this.isDown.set(false);
196243
this.element.nativeElement.classList.remove('active');
197244

198-
// If we've dragged significantly, prevent any click
199245
if (this.isDragging() || this.moveDistance() >= this.clickThreshold) {
200246
this.suppressClick.set(true);
201247
event.preventDefault();
202248

203-
// Start momentum scrolling
204-
this.autoScroll();
249+
this.cancelAnimation();
205250
}
206251
}
207252

@@ -212,49 +257,45 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
212257
const x = event.touches[0].pageX;
213258
const delta = x - this.startX();
214259

215-
// If moved more than threshold, consider it a drag
216260
if (Math.abs(delta) > 5 && !this.isDragging()) {
217261
this.isDragging.set(true);
218262
}
219263

220-
// Calculate velocity for momentum scrolling
221264
const now = Date.now();
222265
const elapsed = now - this.timestamp();
223266

224-
// Update timestamp
225267
this.timestamp.set(now);
226268

227-
// Calculate movement - smoother for touch
228-
const currentScrollLeft = this.scrollLeft() - delta * 1.2; // Slightly faster for touch
269+
const touchSpeedMultiplier = 1.0;
270+
const currentScrollLeft = this.scrollLeft() - (delta * touchSpeedMultiplier);
229271
this.element.nativeElement.scrollLeft = currentScrollLeft;
230272

231-
// Update values for next move
232273
this.startX.set(x);
233274
this.scrollLeft.set(this.element.nativeElement.scrollLeft);
234275

235-
// Calculate velocity (pixels/ms)
236276
if (elapsed > 0) {
237-
const v = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocity();
277+
const v = 0.6 * (1000 * delta / (1 + elapsed)) + 0.4 * this.velocity();
238278
this.velocity.set(v);
239279
}
240280

241-
// Track total distance moved for distinguishing taps from drags
242281
this.moveDistance.set(this.moveDistance() + Math.abs(delta));
243282
}
244283

245-
// Momentum scrolling with physics-based deceleration
246284
private autoScroll(): void {
247-
const amplitude = this.velocity() * 0.8; // Adjust amplitude for desired momentum
285+
const amplitude = this.velocity() * this.velocityFactor;
248286
const initialPosition = this.element.nativeElement.scrollLeft;
249-
const targetPosition = initialPosition - amplitude * 15; // Adjust multiplier for momentum distance
250287

251-
this.amplitude.set(targetPosition - initialPosition);
252-
this.target.set(targetPosition);
288+
const targetPosition = initialPosition - amplitude * this.momentumMultiplier;
289+
290+
const maxScrollLeft = this.element.nativeElement.scrollWidth - this.element.nativeElement.clientWidth;
291+
const boundedTarget = Math.max(0, Math.min(targetPosition, maxScrollLeft));
292+
293+
this.amplitude.set(boundedTarget - initialPosition);
294+
this.target.set(boundedTarget);
253295
this.timestamp.set(Date.now());
254296

255297
this.animationActive = true;
256298

257-
// Run animation outside Angular for better performance
258299
this.ngZone.runOutsideAngular(() => {
259300
cancelAnimationFrame(this.frame());
260301
this.frame.set(requestAnimationFrame(() => this.autoScrollStep()));
@@ -279,5 +320,7 @@ export class DragScrollDirective implements AfterViewInit, OnDestroy {
279320
private cancelAnimation(): void {
280321
this.animationActive = false;
281322
cancelAnimationFrame(this.frame());
323+
324+
this.velocity.set(0);
282325
}
283326
}

0 commit comments

Comments
 (0)