Skills/Editor Toolkit/Fit Scale Element

ef-fit-scale

Attributes

paused
boolean

Pause scale calculations (useful during animations)

Default:false

Properties

contentChild
HTMLElement | null

First content element (excludes style, script, display:none)

scaleInfo
{ scale: number, containerWidth: number, containerHeight: number, contentWidth: number, contentHeight: number }

Current scale calculation result

Functions

computeFitScale(input: ScaleInput): ScaleOutput | null

Pure function to compute scale and centering for given dimensions

Returns: ScaleOutput | null

Responsive container that scales content to fit while preserving aspect ratio.

Basic Usage

Scale content to fit container:

<div class="w-full h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-100">
<ef-fit-scale class="w-full h-full">
<div class="w-[1920px] h-[1080px] bg-blue-500 text-white flex items-center justify-center text-4xl">
1920×1080 Content
</div>
</ef-fit-scale>
</div>

Content scales down to fit the container while maintaining its 16:9 aspect ratio.

How It Works

Fit scale automatically:

  1. Observes container size
  2. Reads content natural dimensions
  3. Calculates scale factor to fit
  4. Centers content with translate
  5. Applies transform to content

Content Detection

Fit scale finds the first content element, ignoring:

  • <style> tags
  • <script> tags
  • <meta> tags
  • Elements with display: none
  • Elements with display: contents
<ef-fit-scale>
<style>/* Ignored */</style>
<script>/* Ignored */</script>
<div><!-- This is the content --></div>
</ef-fit-scale>

Natural Dimensions

For media elements (ef-video, ef-image), fit scale uses natural dimensions:

<ef-fit-scale class="w-full h-[400px]">
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4"></ef-video>
</ef-fit-scale>

Video scales based on its native resolution, not CSS dimensions.

getNaturalDimensions Method

Media elements can provide natural dimensions:

class MyMediaElement extends HTMLElement {
getNaturalDimensions() {
return { width: 1920, height: 1080 };
}
}

Fit scale calls this method if available.

Responsive Scaling

Fit scale updates automatically when:

  • Container resizes
  • Content resizes
  • Content child changes
<div class="flex flex-col gap-4">
<div class="w-full h-[300px] border border-gray-300 rounded overflow-hidden bg-gray-100">
<ef-fit-scale class="w-full h-full">
<div class="w-[800px] h-[600px] bg-green-500 text-white flex items-center justify-center text-2xl">
800×600 (4:3)
</div>
</ef-fit-scale>
</div>
<div class="w-full h-[300px] border border-gray-300 rounded overflow-hidden bg-gray-100">
<ef-fit-scale class="w-full h-full">
<div class="w-[1920px] h-[1080px] bg-blue-500 text-white flex items-center justify-center text-2xl">
1920×1080 (16:9)
</div>
</ef-fit-scale>
</div>
</div>

Different aspect ratios scale correctly.

Scale Calculation

Fit scale uses contain logic (like object-fit: contain):

import { computeFitScale } from '@editframe/elements';
const result = computeFitScale({
containerWidth: 800,
containerHeight: 600,
contentWidth: 1920,
contentHeight: 1080
});
// result = {
// scale: 0.4166, // Scale down to fit
// translateX: 0, // Centered horizontally
// translateY: 75 // Centered vertically
// }

Scale Formula

const containerRatio = containerWidth / containerHeight;
const contentRatio = contentWidth / contentHeight;
const scale = containerRatio > contentRatio
? containerHeight / contentHeight // Limited by height
: containerWidth / contentWidth; // Limited by width

Centering

Content is centered after scaling:

const scaledWidth = contentWidth * scale;
const scaledHeight = contentHeight * scale;
const translateX = (containerWidth - scaledWidth) / 2;
const translateY = (containerHeight - scaledHeight) / 2;

Applied Transform

Fit scale applies transform to content child:

.content {
width: 1920px;
height: 1080px;
transform: translate(0px, 75px) scale(0.4166);
transform-origin: top left;
}

Transform origin is top-left for correct positioning.

Pause Calculations

Pause scale updates during animations:

<ef-fit-scale paused>
<!-- Scale calculations paused -->
</ef-fit-scale>
const fitScale = document.querySelector('ef-fit-scale');
// Pause
fitScale.paused = true;
// Resume
fitScale.paused = false; // Recalculates immediately

Zero Dimension Warning

Fit scale warns when container has zero dimensions:

// Console warning if container is 0×0
// "Container has zero dimensions (0×0). Content will be invisible."

Ensure all ancestors have resolved height:

html, body {
height: 100%;
}

Scale Info

Access current scale calculation:

const fitScale = document.querySelector('ef-fit-scale');
console.log(fitScale.scaleInfo);
// {
// scale: 0.4166,
// containerWidth: 800,
// containerHeight: 600,
// contentWidth: 1920,
// contentHeight: 1080
// }

Content Child

Access the content element:

const fitScale = document.querySelector('ef-fit-scale');
const content = fitScale.contentChild;
console.log(content.tagName); // 'EF-VIDEO'

Observers

Fit scale uses three observers:

  1. Container ResizeObserver: Tracks container size
  2. Content ResizeObserver: Tracks content size
  3. MutationObserver: Tracks child list changes

All observers clean up on disconnect.

Video Integration

For ef-video elements, fit scale sets explicit canvas dimensions:

// Breaks circular dependency
canvas.style.width = `${naturalWidth}px`;
canvas.style.height = `${naturalHeight}px`;

This prevents video from collapsing to 0×0 when using width: auto.

Layout Properties

Fit scale applies container styles:

ef-fit-scale {
display: grid;
width: 100%;
height: 100%;
grid-template-columns: 100%;
grid-template-rows: 100%;
overflow: hidden;
box-sizing: border-box;
contain: layout paint style;
position: relative;
}

Grid layout simplifies content positioning.

Performance

Scale calculations use:

  • Fixed precision: Values rounded to 4 decimal places
  • Change detection: Only updates when values change
  • RAF deferral: Initial calculation deferred to allow layout
// Internal precision handling
transform: `translate(${translateX.toFixed(4)}px, ${translateY.toFixed(4)}px) scale(${scale.toFixed(4)})`;

Cleanup

Fit scale removes transform when disconnected:

// On disconnect
content.style.width = '';
content.style.height = '';
content.style.transform = '';
content.style.transformOrigin = '';

Content returns to normal layout.

Comparison with object-fit

ef-fit-scale:

  • Works with any element
  • JavaScript-based scaling
  • Provides scale info
  • Can be paused

CSS object-fit:

  • Only works with replaced elements (img, video)
  • CSS-based scaling
  • No JavaScript API
  • Always active

Nested Fit Scale

Multiple fit scales can nest:

<ef-fit-scale class="w-full h-full">
<ef-timegroup class="w-[1920px] h-[1080px]">
<ef-fit-scale class="w-full h-full">
<ef-video src="video.mp4"></ef-video>
</ef-fit-scale>
</ef-timegroup>
</ef-fit-scale>

Each fit scale scales its own content independently.

Behavior

  • Automatically observes container and content size changes
  • Recalculates scale on resize using ResizeObserver
  • Centers content within container
  • Maintains content aspect ratio
  • Uses CSS transform for scaling (GPU-accelerated)
  • Handles nested content elements
  • Ignores non-visible elements (display: none, etc.)

Important Notes

  • FitScale is a light DOM component (no shadow DOM)
  • Content should have explicit dimensions (width/height)
  • Works with any content, not just Editframe elements
  • Scale updates automatically on window resize
  • Use paused attribute to temporarily stop recalculation
  • Returns null from computeFitScale for invalid dimensions
  • Uses contain: layout paint style for performance
  • Content is centered both horizontally and vertically