Engineering Journal
Pdf Processor
Pdf Processor

Under the Hood: Responsive Drag, Touch Events, and CSS Cascade in the Diff UI

2026-06-04

TLDR

Redesigning multi-pane diff UIs requires solving three interconnected front-end challenges: handling responsive panel axis switches (row vs column) without hardcoding viewport width breakpoints, supporting touch drag interactions without triggering browser scroll behavior, and preventing layout utility classes from overriding tab visibility state.
Diff UI Engineering ChallengeNaive ApproachReliable implementation
Responsive Axis Draggingwindow.innerWidth <= 1024 checkRead getComputedStyle($el).flexDirection
Touch Event HandlingDefault touchmove listenerAdd { passive: false } & call preventDefault()
Tab Visibility ControlGeneral .diff-layout { display: flex }Compound selector .view-panel.diff-layout

Technical problem statements & solutions

1. Responsive axis dragging via computed flex direction

Using window.innerWidth to determine drag direction breaks when responsive layouts resize dynamically or override CSS breakpoints.

Query computed flex direction at drag start using getComputedStyle():

// Read computed style to determine active flex direction dynamically
function getActiveFlexDirection($layoutContainer) {
  const style = window.getComputedStyle($layoutContainer[0]);
  return style.flexDirection === 'column' ? 'vertical' : 'horizontal';
}

function handleDividerDragStart(e, $layoutContainer) { const isVertical = getActiveFlexDirection($layoutContainer) === 'vertical'; const firstPane = $layoutContainer.find('.vd-pane').first(); const startSizePx = isVertical ? firstPane.outerHeight() : firstPane.outerWidth(); const startPointerPos = extractPointerCoordinate(e, isVertical);

// Bind move and end listeners to document to handle fast drag movements const handleMove = (moveEvt) => { const currentPos = extractPointerCoordinate(moveEvt, isVertical); const delta = currentPos - startPointerPos; const totalSize = isVertical ? $layoutContainer.outerHeight() : $layoutContainer.outerWidth();

const newSizePx = Math.max(120, Math.min(totalSize - 120, startSizePx + delta)); const percentage = (newSizePx / totalSize) * 100;

// Use flex-basis to support both row and column layouts automatically $layoutContainer.find('.vd-pane').eq(0).css('flex', 0 0 ${percentage}%); $layoutContainer.find('.vd-pane').eq(1).css('flex', 0 0 ${100 - percentage}%); };

bindDragListeners(handleMove); }


2. Unified pointer coordinate extraction & touch events

Prevent mobile browsers from intercepting divider drag gestures as page scroll events by registering touch listeners with { passive: false }:
// Extract ClientX or ClientY across Mouse and Touch events
function extractPointerCoordinate(e, isVertical) {
  const point = e.touches?.[0] ?? e;
  return isVertical ? point.clientY : point.clientX;
}

// Bind non-passive listeners to allow e.preventDefault() on touchmove dividerElement.addEventListener('touchstart', (e) => { e.preventDefault(); handleDividerDragStart(e, $layout); }, { passive: false });

document.addEventListener('touchmove', (e) => { if (isDraggingDivider) { e.preventDefault(); // Prevents touch page scrolling executeDragMove(e); } }, { passive: false });


3. CSS specificity rules for visibility-toggled panels

Utility layout classes setting display: flex overwrite tab system display: none rules when CSS specificity scores match ((0, 1, 0)).
/ DEFECTIVE: Overwrites .view-panel { display: none } due to later source order /
.diff-layout {
  display: flex;
  flex-direction: column;
}

/ REFACTORED: Higher specificity (0, 2, 0) and avoids setting display directly / .view-panel.diff-layout { flex-direction: column; }

By removing display: flex from .diff-layout, visibility control remains strictly owned by the tab system (.view-panel.active { display: flex }).

Rule of thumb: Read computed flex direction dynamically at drag start, register touchmove listeners with { passive: false }, and keep visibility control separate from layout helper classes.
Read this post in the full Engineering Journal →