Fix split-compare clip-path angle to match divider's skewX(-10deg)

The divider uses a fixed CSS angle (skewX(-10deg)) but the clip-path
mask used a fixed percentage offset (skewOffset=8), which only matches
at one specific aspect ratio. Now computes skewOffset dynamically from
the container's actual dimensions via ResizeObserver.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-17 08:04:24 -07:00
co-authored by Claude Opus 4.6
parent b5af865d84
commit a3633c66f0
4 changed files with 3962 additions and 17 deletions
+3933 -1
View File
File diff suppressed because one or more lines are too long
+2 -9
View File
@@ -221,10 +221,7 @@ function updateTerminal(cmd, container, allCommands) {
const splitComparison = container.querySelector('.demo-split-comparison');
if (splitComparison) {
currentSplitInstance = initSplitCompare(splitComparison, {
defaultPosition: 50,
skewOffset: 8,
defaultPosition: 50
});
}
initCommandDemo(cmd.id, container);
@@ -293,7 +290,6 @@ function setupMobileInteractions(commands) {
if (initialSplit) {
currentSplitInstance = initSplitCompare(initialSplit, {
defaultPosition: 50,
skewOffset: 6,
minPosition: 10,
maxPosition: 90
});
@@ -334,10 +330,7 @@ function setupMobileInteractions(commands) {
const splitComparison = demoArea.querySelector('.demo-split-comparison');
if (splitComparison) {
currentSplitInstance = initSplitCompare(splitComparison, {
defaultPosition: 50,
skewOffset: 6,
defaultPosition: 50
});
}
initCommandDemo(cmdId, demoArea);
+1 -2
View File
@@ -5,8 +5,7 @@ export function initLensEffect() {
if (!container) return;
initSplitCompare(container, {
defaultPosition: 70,
skewOffset: 8
defaultPosition: 70
});
}
+26 -5
View File
@@ -10,9 +10,7 @@
export function initSplitCompare(container, options = {}) {
const {
defaultPosition = 70,
skewOffset = 8,
minPosition = -skewOffset,
maxPosition = 100 + skewOffset,
skewAngle = 10, // Degrees — matches CSS skewX(-10deg) on .split-divider
lerpSpeed = 0.15,
animationThreshold = 40, // Re-trigger animations when crossing this threshold
onCrossThreshold = null // Callback when crossing threshold toward "after" side
@@ -24,6 +22,26 @@ export function initSplitCompare(container, options = {}) {
if (!splitContainer || !splitAfter || !splitDivider) return null;
// Compute skewOffset from container dimensions so clip-path angle matches CSS skewX
const tanAngle = Math.tan(skewAngle * Math.PI / 180);
let skewOffset = 8; // fallback
function recalcSkewOffset() {
const rect = splitContainer.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
skewOffset = 50 * rect.height * tanAngle / rect.width;
}
}
recalcSkewOffset();
const resizeObserver = new ResizeObserver(recalcSkewOffset);
resizeObserver.observe(splitContainer);
let minPosition = -skewOffset;
let maxPosition = 100 + skewOffset;
if (options.minPosition != null) minPosition = options.minPosition;
if (options.maxPosition != null) maxPosition = options.maxPosition;
let isHovering = false;
let currentX = defaultPosition;
let targetX = defaultPosition;
@@ -31,7 +49,9 @@ export function initSplitCompare(container, options = {}) {
let wasAboveThreshold = defaultPosition > animationThreshold;
function updateSplit(percent) {
const clampedX = Math.max(minPosition, Math.min(maxPosition, percent));
const minPos = options.minPosition != null ? minPosition : -skewOffset;
const maxPos = options.maxPosition != null ? maxPosition : 100 + skewOffset;
const clampedX = Math.max(minPos, Math.min(maxPos, percent));
// Check if we crossed the threshold toward the "after" side (moving left)
const isAboveThreshold = clampedX > animationThreshold;
@@ -42,7 +62,7 @@ export function initSplitCompare(container, options = {}) {
}
wasAboveThreshold = isAboveThreshold;
// Angled clip-path matching divider's skewX(-10deg)
// Angled clip-path matching divider's skewX — offset computed from actual dimensions
splitAfter.style.clipPath = `polygon(${clampedX + skewOffset}% 0%, 100% 0%, 100% 100%, ${clampedX - skewOffset}% 100%)`;
splitDivider.style.left = `${clampedX}%`;
}
@@ -184,6 +204,7 @@ export function initSplitCompare(container, options = {}) {
splitContainer.removeEventListener('touchend', handleTouchEnd);
splitContainer.removeEventListener('touchmove', handleTouchMove);
if (animationId) cancelAnimationFrame(animationId);
resizeObserver.disconnect();
},
setPosition(percent) {
targetX = percent;