Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions docs/src/pages/docs/en/components/media-preview-thumbnail.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,60 @@ For more details on how thumbnails are integrated and controlled, see [`<media-t
mediapreviewcoords="284 640 284 160"
></media-preview-thumbnail>
```

## Sizing and Customization

The preview thumbnail size is controlled via CSS custom properties on the [`<media-time-range>`](media-time-range) component:

```css
media-time-range {
--media-preview-thumbnail-max-width: 200px;
--media-preview-thumbnail-max-height: 200px;
}
```

### Aspect Ratio Behavior

By default, thumbnails maintain their original aspect ratio using `--media-preview-thumbnail-object-fit: contain` (the default). This means:

- The thumbnail scales to fit within the max/min dimensions
- The original aspect ratio is preserved
- Setting equal width and height won't create a square if the source isn't square

**Example with default behavior:**

```css
media-time-range {
--media-preview-thumbnail-max-width: 200px;
--media-preview-thumbnail-max-height: 200px;
/* Maintains aspect ratio - won't be square unless source is square */
}
```

### Independent Width/Height Scaling

To allow independent width and height scaling (useful for square thumbnails or vertical videos), use `object-fit: fill`:

```css
media-time-range {
--media-preview-thumbnail-max-width: 200px;
--media-preview-thumbnail-max-height: 200px;
--media-preview-thumbnail-object-fit: fill;
/* Creates square thumbnails by stretching to fill */
}
```

### Vertical Video Thumbnails

For vertical/portrait videos (like TikTok or Instagram Stories), use taller dimensions with `object-fit: fill`:

```css
media-time-range {
--media-preview-thumbnail-max-width: 160px;
--media-preview-thumbnail-max-height: 384px;
--media-preview-thumbnail-object-fit: fill;
}
```


**Note:** Using `fill` may cause image stretching if the aspect ratio doesn't match the source thumbnails.
42 changes: 32 additions & 10 deletions src/js/media-preview-thumbnail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function getTemplateHTML(_attrs: Record<string, string>) {
*
* @cssproperty [--media-preview-thumbnail-display = inline-block] - `display` property of display.
* @cssproperty [--media-control-display = inline-block] - `display` property of control.
* @cssproperty [--media-preview-thumbnail-object-fit = contain] - Controls how the thumbnail scales within its container. `contain` (default) maintains aspect ratio, `fill` allows independent width/height scaling.
*/
class MediaPreviewThumbnail extends globalThis.HTMLElement {
static shadowRootOptions = { mode: 'open' as ShadowRootMode };
Expand Down Expand Up @@ -153,27 +154,48 @@ class MediaPreviewThumbnail extends globalThis.HTMLElement {

const computedStyle = getComputedStyle(this);
const { maxWidth, maxHeight, minWidth, minHeight } = computedStyle;
const maxRatio = Math.min(parseInt(maxWidth) / w, parseInt(maxHeight) / h);
const minRatio = Math.max(parseInt(minWidth) / w, parseInt(minHeight) / h);

// maxRatio scales down and takes priority, minRatio scales up.
const isScalingDown = maxRatio < 1;
const scale = isScalingDown ? maxRatio : minRatio > 1 ? minRatio : 1;
// Check if user wants independent width/height scaling (fill mode)
// Default is 'contain' which preserves aspect ratio
const objectFit = computedStyle.getPropertyValue('--media-preview-thumbnail-object-fit').trim() || 'contain';

let scaleX: number;
let scaleY: number;

if (objectFit === 'fill') {
const maxRatioX = parseInt(maxWidth) / w;
const maxRatioY = parseInt(maxHeight) / h;
const minRatioX = parseInt(minWidth) / w;
const minRatioY = parseInt(minHeight) / h;

scaleX = maxRatioX < 1 ? maxRatioX : Math.max(maxRatioX, minRatioX);
scaleY = maxRatioY < 1 ? maxRatioY : Math.max(maxRatioY, minRatioY);
} else {
const maxRatio = Math.min(parseInt(maxWidth) / w, parseInt(maxHeight) / h);
const minRatio = Math.max(parseInt(minWidth) / w, parseInt(minHeight) / h);

const isScalingDown = maxRatio < 1;
const scale = isScalingDown ? maxRatio : minRatio > 1 ? minRatio : 1;

scaleX = scale;
scaleY = scale;
}

const { style } = getOrInsertCSSRule(this.shadowRoot, ':host');
const imgStyle = getOrInsertCSSRule(this.shadowRoot, 'img').style;
const img = this.shadowRoot.querySelector('img');

// Revert one set of extremum to its initial value on a known scale direction.
const isScalingDown = Math.min(scaleX, scaleY) < 1;
const extremum = isScalingDown ? 'min' : 'max';
style.setProperty(`${extremum}-width`, 'initial', 'important');
style.setProperty(`${extremum}-height`, 'initial', 'important');
style.width = `${w * scale}px`;
style.height = `${h * scale}px`;
style.width = `${w * scaleX}px`;
style.height = `${h * scaleY}px`;

const resize = () => {
imgStyle.width = `${this.imgWidth * scale}px`;
imgStyle.height = `${this.imgHeight * scale}px`;
imgStyle.width = `${this.imgWidth * scaleX}px`;
imgStyle.height = `${this.imgHeight * scaleY}px`;
imgStyle.display = 'block';
};

Expand All @@ -190,7 +212,7 @@ class MediaPreviewThumbnail extends globalThis.HTMLElement {
}

resize();
imgStyle.transform = `translate(-${x * scale}px, -${y * scale}px)`;
imgStyle.transform = `translate(-${x * scaleX}px, -${y * scaleY}px)`;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/js/media-time-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ const calcTimeFromRangeValue = (
* @cssproperty --media-preview-thumbnail-max-height - `max-height` of range preview thumbnail.
* @cssproperty --media-preview-thumbnail-min-width - `min-width` of range preview thumbnail.
* @cssproperty --media-preview-thumbnail-min-height - `min-height` of range preview thumbnail.
* @cssproperty --media-preview-thumbnail-object-fit - Controls scaling behavior: `contain` (default, maintains aspect ratio) or `fill` (allows independent width/height scaling).
* @cssproperty --media-preview-thumbnail-border-radius - `border-radius` of range preview thumbnail.
* @cssproperty --media-preview-thumbnail-border - `border` of range preview thumbnail.
*
Expand Down