Skip to content

Comments

fix: horizontal scroll flicker#630

Open
drdreo wants to merge 2 commits intosiemens:mainfrom
drdreo:fix/horizontal-scroll-sync
Open

fix: horizontal scroll flicker#630
drdreo wants to merge 2 commits intosiemens:mainfrom
drdreo:fix/horizontal-scroll-sync

Conversation

@drdreo
Copy link

@drdreo drdreo commented Feb 24, 2026

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior? (You can also link to an open issue here)

During horizontal scrolling, the header visually lags behind the body. It briefly resets to the left before snapping back into position, causing a noticeable flicker. This is caused by the requestAnimationFrame wrapper in the scroll handler and the header using a CSS translate transform that goes through Angular change detection, rather than scrolling in sync with the body.

This is easily verifiable by pulling the demo page changes (scrolling.component.ts) and scrolling horizontally, one will observe a clear stutter / flicker. I have only observed this with a more real-world setup: custom templates, dynamically renderd columns, etc.

What is the new behavior?

Header and body scroll in sync via direct DOM manipulation, bypassing Angular CD entirely. The translateX/offsetX approach on the header is removed. I added a demo with dynamic columns and custom templates to the scrolling example to validate the fix under real-world conditions.

Does this PR introduce a breaking change? (check one with "x")

  • Yes
  • No

drdreo and others added 2 commits February 24, 2026 17:44
Replace the requestAnimationFrame-based scroll handler with synchronous reads and remove the header's translateX/offsetX approach. Instead, set the header element's scrollLeft directly from the body's scroll event, eliminating the visual lag between header and body during horizontal scrolling.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add a second datatable to the scrolling demo that generates columns dynamically via @for over a signal-based config. Each column uses custom header and cell templates with @switch for varied rendering (text, emphasis, progress bar, badge, link), reproducing the real-world pattern that triggered the horizontal scroll flicker.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@drdreo drdreo requested a review from a team as a code owner February 24, 2026 16:50
@gemini-code-assist
Copy link

Summary of Changes

Hello @drdreo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a horizontal scroll flicker issue in the datatable component. The solution optimizes scroll synchronization between the table body and header by removing a requestAnimationFrame wrapper and implementing direct DOM manipulation for header scrolling. This approach ensures that scroll updates are applied immediately, bypassing Angular's change detection, which results in a significantly smoother user experience, particularly with tables that have dynamic or custom content.

Highlights

  • Performance Improvement: Eliminated the requestAnimationFrame wrapper in the scroller component's onScrolled method to ensure immediate scroll updates and prevent visual lag.
  • Synchronized Scrolling: Implemented direct DOM manipulation to synchronize the header's horizontal scroll position with the body, bypassing Angular's change detection for smoother and more responsive scrolling.
  • Simplified Header Logic: Removed the offsetX input and the associated translateX CSS transform logic from the header component, as direct DOM manipulation now handles the horizontal scroll positioning.
  • Enhanced Demo Coverage: Added a new demo case to the scrolling example component, featuring dynamic columns and custom templates, to thoroughly validate the fix under real-world and complex table configurations.
Changelog
  • projects/ngx-datatable/src/lib/components/body/scroller.component.ts
    • Removed the requestAnimationFrame call from the onScrolled method to allow immediate scroll position updates.
  • projects/ngx-datatable/src/lib/components/datatable.component.html
    • Removed the [offsetX] binding from the ngx-datatable-header component.
  • projects/ngx-datatable/src/lib/components/datatable.component.ts
    • Added logic within onBodyScroll to directly set the scrollLeft property of the header element, ensuring synchronized horizontal scrolling.
  • projects/ngx-datatable/src/lib/components/header/header.component.spec.ts
    • Updated a test case description and assertions to reflect the removal of translateX for center group columns, verifying the new scroll synchronization mechanism.
  • projects/ngx-datatable/src/lib/components/header/header.component.ts
    • Removed the offsetX input and the translateX CSS transform logic from the calcStylesByGroup method.
  • src/app/basic/scrolling.component.ts
    • Imported additional directives (DataTableColumnCellDirective, DataTableColumnHeaderDirective).
    • Added a new demo section for dynamic columns with custom templates to validate the fix.
    • Defined a ColumnConfig interface to support the new dynamic column demo.
Activity
  • No human activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@CLAassistant
Copy link

CLAassistant commented Feb 24, 2026

CLA assistant check
All committers have signed the CLA.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively resolves the horizontal scroll flicker by synchronizing the header and body scroll positions via direct DOM manipulation. The removal of requestAnimationFrame and the CSS translateX transform in favor of directly setting scrollLeft is a direct and appropriate fix for the described lag. The changes are well-implemented, and the addition of a more complex demo case is a great way to validate the solution. I've included a couple of suggestions for minor improvements related to Angular best practices.

// Sync header scroll position directly via DOM to avoid Angular CD lag
const headerEl = this._headerElement()?.nativeElement;
if (headerEl) {
headerEl.scrollLeft = event.offsetX;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better platform abstraction and to adhere to Angular best practices, it's recommended to use Renderer2 for direct DOM manipulation instead of accessing nativeElement properties directly. This helps ensure your component can work in environments without direct DOM access, like server-side rendering.

You would need to inject Renderer2:

import { Renderer2 } from '@angular/core';
// ...
private renderer = inject(Renderer2);

And then use it here.

      this.renderer.setProperty(headerEl, 'scrollLeft', event.offsetX);

Comment on lines +80 to +108
@switch (col.type) {
@case ('progress') {
<div style="display: flex; align-items: center; gap: 8px;">
<div
style="background: #4caf50; height: 8px; border-radius: 4px;"
[style.width.px]="value"
></div>
<span>{{ value }}</span>
</div>
}
@case ('badge') {
<span
style="background: #e3f2fd; padding: 2px 8px; border-radius: 12px; font-size: 12px;"
>
{{ value }}
</span>
}
@case ('link') {
<span style="color: #1976d2; text-decoration: underline; cursor: pointer;">{{
value
}}</span>
}
@case ('emphasis') {
<em>{{ value }}</em>
}
@default {
{{ value }}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this is a demo component, there are multiple inline styles being used. For better maintainability and separation of concerns, consider moving these styles to a dedicated stylesheet and using CSS classes. This would make the template cleaner and the styles easier to manage. For example, you could add a styleUrls property to your @Component decorator and define classes for progress, badge, link, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants