Skip to content

Header and foorter Height Calculation Issue with CSS Transform Scale #4804

@cadjou

Description

@cadjou

Relative issue : #4364

Bug Description

Tabulator incorrectly calculates header heights when the table container is inside an element with CSS transform: scale() applied. This results in misaligned headers and improper table rendering.

Environment

  • Tabulator Version: 6.3
  • Browser: Chrome/Edge/Firefox (reproduced across browsers)
  • OS: Windows 11
  • Application Type: Electron app with pan/zoom workspace

Reproduction Steps

  1. Create a container with CSS transform scale:
.workspace {
    transform: scale(0.5); /* Any scale different from 1 */
    transform-origin: top left;
}
  1. Initialize a Tabulator table inside this scaled container:
var table = new Tabulator("#example-table", {
    height: "300px",
    layout: "fitDataFill",
    data: [...],
    columns: [...]
});
  1. Observe that header heights are calculated incorrectly
  2. The issue is more visible with complex headers or when resizing

Expected Behavior

Headers should render correctly regardless of parent container's CSS transform scale.

Actual Behavior

  • Header heights appear compressed or stretched
  • Column alignments are misaligned with header text
  • Pagination controls are also affected with incorrect sizing and positioning
  • Table rendering is visually broken
  • Issue persists even after calling table.redraw() or table.redraw(true)

Code Example

HTML Structure

<div class="workspace" style="transform: scale(0.75)">
    <div class="widget">
        <div id="my-table"></div>
    </div>
</div>

JavaScript

const table = new Tabulator("#my-table", {
    height: "250px",
    layout: "fitDataFill",
    data: [
        {name: "John", age: 25, city: "New York"},
        {name: "Jane", age: 30, city: "London"}
    ],
    columns: [
        {title: "Name", field: "name"},
        {title: "Age", field: "age"},
        {title: "City", field: "city"}
    ]
});

CSS

.workspace {
    transform: scale(0.75);
    transform-origin: top left;
    width: 100vw;
    height: 100vh;
}

.widget {
    position: absolute;
    width: 400px;
    height: 300px;
}

Root Cause Analysis

The issue appears to be related to how Tabulator calculates dimensions using getBoundingClientRect() or similar DOM measurement methods. When a parent element has transform: scale(), these measurements return scaled values, but Tabulator's internal calculations don't account for this scaling factor.

Workarounds Attempted

  1. Manual redraw after initialization: table.redraw(true) - No effect
  2. Delayed initialization: setTimeout(() => table.redraw(), 100) - No effect
  3. ResizeObserver: Monitoring container size changes - Partial improvement but still buggy
  4. CSS zoom instead of transform: Not viable for our use case
  5. Recalculating dimensions manually: Complex and unreliable

Impact

This bug affects any application that uses CSS transforms for:

  • Zoom/pan interfaces
  • Responsive scaling
  • Canvas-based applications
  • Dashboard widgets with scaling

Suggested Solution

Tabulator should detect when it's inside a scaled container and adjust its dimension calculations accordingly. This could be done by:

  1. Walking up the DOM tree to detect transform: scale() on parent elements
  2. Calculating the cumulative scale factor
  3. Adjusting internal dimension calculations by this factor

Proposed Patch (Experimental)

Note: An experimental patch has been developed and tested locally. This patch requires proper validation and integration by the Tabulator development team.

Patch Overview

The patch adds three utility functions to detect and correct CSS transform scale issues:

// Utility function to correct dimensions when CSS transform scale is applied
function getCorrectedDimensions(element, dimension) {
    if (!element) return 0;
    const rect = element.getBoundingClientRect();
    const scaleFactors = getTransformScaleFactors(element);

    switch(dimension) {
        case 'height':
            return Math.ceil(rect.height / scaleFactors.y);
        case 'width':
            return Math.ceil(rect.width / scaleFactors.x);
        default:
            return rect;
    }
}

// Get cumulative CSS transform scale factors from element and all parents
function getTransformScaleFactors(element) {
    let scaleX = 1, scaleY = 1;
    let current = element;

    while (current && current !== document.body) {
        const style = window.getComputedStyle(current);
        const transform = style.transform;

        if (transform && transform !== 'none') {
            const matrix = transform.match(/matrix\(([^)]+)\)/);
            const matrix3d = transform.match(/matrix3d\(([^)]+)\)/);

            if (matrix) {
                const values = matrix[1].split(',').map(parseFloat);
                scaleX *= values[0] || 1;
                scaleY *= values[3] || 1;
            } else if (matrix3d) {
                const values = matrix3d[1].split(',').map(parseFloat);
                scaleX *= values[0] || 1;
                scaleY *= values[5] || 1;
            }
        }
        current = current.parentElement;
    }

    return { x: scaleX, y: scaleY };
}

// Get corrected bounding client rect for range selections and positioning
function getCorrectedRect(element) {
    if (!element) return { top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0 };

    const rect = element.getBoundingClientRect();
    const scaleFactors = getTransformScaleFactors(element);

    return {
        top: rect.top,
        bottom: rect.top + rect.height / scaleFactors.y,
        left: rect.left,
        right: rect.left + rect.width / scaleFactors.x,
        width: rect.width / scaleFactors.x,
        height: rect.height / scaleFactors.y
    };
}

Key Modifications Required

  1. Column height calculations (around line 1509):

    // Replace:
    return Math.ceil(this.element.getBoundingClientRect().height);
    // With:
    return getCorrectedDimensions(this.element, 'height');
  2. Vertical fill mode calculations (around line 5785):

    // Replace getBoundingClientRect() calls with getCorrectedDimensions()
    let columnHeight = getCorrectedDimensions(this.table.columnManager.getElement(), 'height');
    let footerHeight = (this.table.footerManager && this.table.footerManager.active && !this.table.footerManager.external) ?
        getCorrectedDimensions(this.table.footerManager.getElement(), 'height') : 0;
    let otherHeight = Math.floor(columnHeight + footerHeight);
  3. Column width fitting (around line 7428):

    // Replace:
    var totalWidth = this.table.rowManager.element.getBoundingClientRect().width;
    // With:
    var totalWidth = getCorrectedDimensions(this.table.rowManager.element, 'width');
  4. Range selection positioning (around lines 27155-27158):

    // Replace getBoundingClientRect() calls with getCorrectedRect()
    rowRect = getCorrectedRect(row.getElement());
    columnRect = getCorrectedRect(column.getElement());
    rowManagerRect = getCorrectedRect(this.table.rowManager.getElement());
    columnManagerRect = getCorrectedRect(this.table.columnManager.getElement());

Testing Results

  • ✅ Headers maintain correct proportions at all scale levels (0.25x to 1.5x)
  • ✅ Pagination controls render correctly regardless of transform scale
  • ✅ Column width calculations work properly in scaled containers
  • ✅ Range selections function correctly
  • ✅ No regression observed in non-scaled environments

Limitations

This patch is experimental and:

  • Requires thorough testing across different browsers and environments
  • Needs performance impact assessment
  • Should be reviewed by the Tabulator development team
  • May need optimization for production use
  • Only handles matrix() and matrix3d() transforms (most common cases)

Live Demo

A complete HTML demonstration file is available that shows the bug in action:

  • Side-by-side comparison: Normal table vs scaled table
  • Interactive scale slider: Test different zoom levels
  • Pagination toggle: See how pagination is also affected
  • Real-time testing: Redraw buttons and data addition to test fixes

The demo file can be opened directly in any browser to reproduce the issue immediately.

tabulator-zoom-bug-demo.html

Additional Information

  • The issue occurs with both programmatic table creation and dynamic data updates
  • It's reproducible with minimal code (see example above)
  • The bug affects the visual rendering but doesn't break functionality
  • Similar issues might exist with other CSS transforms (rotate, skew, etc.)
  • Pagination controls are equally affected with incorrect sizing and positioning

Related Issues

This might be related to similar issues in other libraries that calculate DOM dimensions inside transformed containers.


Note: This issue significantly impacts user experience in applications with zoom functionality. An experimental patch is available for testing but requires proper validation and integration by the development team.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Suggested FeatureA suggested feature that is waiting review

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions