mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): contain embedded media inside its box instead of overflowing (#1871)
This commit is contained in:
1 parent
5834e32aa5
commit
eda29c0e34
1 file changed
+61
-72
@@ -23,15 +23,56 @@ interface DimensionResult {
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export const MEDIA_MAX_WIDTH = 550;
|
||||
export const MEDIA_MAX_HEIGHT = 350;
|
||||
|
||||
const DEFAULT_OPTIONS: Required<DimensionOptions> = {
|
||||
maxWidth: 550,
|
||||
maxHeight: 400,
|
||||
maxWidth: MEDIA_MAX_WIDTH,
|
||||
maxHeight: MEDIA_MAX_HEIGHT,
|
||||
preserve: false,
|
||||
forceScale: false,
|
||||
aspectRatio: true,
|
||||
responsive: true,
|
||||
};
|
||||
|
||||
export interface FitMediaInput extends MediaDimensions {
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
}
|
||||
|
||||
export function fitMediaWithinBounds({
|
||||
width,
|
||||
height,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
minWidth = 0,
|
||||
minHeight = 0,
|
||||
}: FitMediaInput): MediaDimensions {
|
||||
if (width === maxWidth && height === maxHeight) {
|
||||
return {width, height};
|
||||
}
|
||||
const widthRatio = width > maxWidth ? maxWidth / width : 1;
|
||||
let fittedWidth = Math.max(Math.round(width * widthRatio), minWidth);
|
||||
let fittedHeight = Math.max(Math.round(height * widthRatio), minHeight);
|
||||
const heightRatio = fittedHeight > maxHeight ? maxHeight / fittedHeight : 1;
|
||||
fittedWidth = Math.max(Math.round(fittedWidth * heightRatio), minWidth);
|
||||
fittedHeight = Math.max(Math.round(fittedHeight * heightRatio), minHeight);
|
||||
return {width: fittedWidth, height: fittedHeight};
|
||||
}
|
||||
|
||||
export function mediaContainRatio({width, height, maxWidth, maxHeight}: FitMediaInput): number {
|
||||
const widthRatio = width > maxWidth ? maxWidth / width : 1;
|
||||
const scaledHeight = Math.round(height * widthRatio);
|
||||
const heightRatio = scaledHeight > maxHeight ? maxHeight / scaledHeight : 1;
|
||||
return Math.min(widthRatio * heightRatio, 1);
|
||||
}
|
||||
|
||||
export function mediaAspectRatioValue({width, height}: MediaDimensions): number {
|
||||
return width / height;
|
||||
}
|
||||
|
||||
export class MediaDimensionCalculator {
|
||||
private options: Required<DimensionOptions>;
|
||||
|
||||
@@ -43,100 +84,49 @@ export class MediaDimensionCalculator {
|
||||
const config = {...this.options, ...options};
|
||||
const safeDimensions = this.normalizeDimensions(dimensions);
|
||||
if (config.preserve) {
|
||||
return this.preserveDimensions(safeDimensions);
|
||||
return this.preserveDimensions(safeDimensions, config);
|
||||
}
|
||||
if (config.forceScale) {
|
||||
return this.forceScaleDimensions(safeDimensions, config);
|
||||
}
|
||||
return this.calculateResponsiveDimensions(safeDimensions, config);
|
||||
return this.containDimensions(safeDimensions, config);
|
||||
}
|
||||
|
||||
public calculateImage(dimensions: MediaDimensions, options?: Omit<DimensionOptions, 'forceScale'>): DimensionResult {
|
||||
return this.calculate(dimensions, {...options, forceScale: false});
|
||||
public calculateImage(dimensions: MediaDimensions, options?: DimensionOptions): DimensionResult {
|
||||
return this.calculate(dimensions, options);
|
||||
}
|
||||
|
||||
public calculateVideo(dimensions: MediaDimensions, options?: Omit<DimensionOptions, 'preserve'>): DimensionResult {
|
||||
return this.calculate(dimensions, {...options, preserve: false});
|
||||
}
|
||||
|
||||
private preserveDimensions(dimensions: MediaDimensions): DimensionResult {
|
||||
private preserveDimensions(dimensions: MediaDimensions, options: Required<DimensionOptions>): DimensionResult {
|
||||
return {
|
||||
style: {
|
||||
width: remFromPx(dimensions.width),
|
||||
aspectRatio: `${dimensions.width}/${dimensions.height}`,
|
||||
maxWidth: '100%',
|
||||
...(options.aspectRatio && {aspectRatio: mediaAspectRatioValue(dimensions)}),
|
||||
},
|
||||
dimensions: {...dimensions},
|
||||
scale: 1,
|
||||
};
|
||||
}
|
||||
|
||||
private forceScaleDimensions(dimensions: MediaDimensions, options: Required<DimensionOptions>): DimensionResult {
|
||||
private containDimensions(dimensions: MediaDimensions, options: Required<DimensionOptions>): DimensionResult {
|
||||
const maxWidth = this.normalizeLimit(options.maxWidth, DEFAULT_OPTIONS.maxWidth);
|
||||
const maxHeight = this.normalizeLimit(options.maxHeight, DEFAULT_OPTIONS.maxHeight);
|
||||
const scale = Math.min(1, maxWidth / dimensions.width, maxHeight / dimensions.height);
|
||||
const scaledDimensions = {
|
||||
width: Math.max(1, Math.round(dimensions.width * scale)),
|
||||
height: Math.max(1, Math.round(dimensions.height * scale)),
|
||||
};
|
||||
const fitted = fitMediaWithinBounds({...dimensions, maxWidth, maxHeight});
|
||||
const scale = mediaContainRatio({...dimensions, maxWidth, maxHeight});
|
||||
return {
|
||||
style: {
|
||||
width: `min(100%, ${remFromPx(scaledDimensions.width)})`,
|
||||
height: 'auto',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
...(options.aspectRatio && {
|
||||
aspectRatio: `${scaledDimensions.width}/${scaledDimensions.height}`,
|
||||
}),
|
||||
maxWidth: `min(100%, ${remFromPx(fitted.width)})`,
|
||||
maxHeight: remFromPx(fitted.height),
|
||||
width: options.responsive ? '100%' : `min(100%, ${remFromPx(fitted.width)})`,
|
||||
display: 'block',
|
||||
...(options.aspectRatio && {aspectRatio: mediaAspectRatioValue(fitted)}),
|
||||
},
|
||||
dimensions: scaledDimensions,
|
||||
dimensions: fitted,
|
||||
scale,
|
||||
};
|
||||
}
|
||||
|
||||
private calculateResponsiveDimensions(
|
||||
dimensions: MediaDimensions,
|
||||
options: Required<DimensionOptions>,
|
||||
): DimensionResult {
|
||||
const maxWidth = this.normalizeLimit(options.maxWidth, DEFAULT_OPTIONS.maxWidth);
|
||||
const maxHeight = this.normalizeLimit(options.maxHeight, DEFAULT_OPTIONS.maxHeight);
|
||||
const isPortrait = dimensions.height > dimensions.width;
|
||||
let style: CSSProperties;
|
||||
let scaledDimensions: MediaDimensions;
|
||||
let scale: number;
|
||||
if (isPortrait) {
|
||||
const targetWidth = Math.max(1, Math.round((maxHeight * dimensions.width) / dimensions.height));
|
||||
const maxAllowedWidth = Math.max(1, Math.min(dimensions.width, maxWidth, targetWidth));
|
||||
scale = maxAllowedWidth / dimensions.width;
|
||||
scaledDimensions = {
|
||||
width: maxAllowedWidth,
|
||||
height: Math.max(1, Math.round(dimensions.height * scale)),
|
||||
};
|
||||
style = {
|
||||
maxWidth: `min(100%, ${remFromPx(maxAllowedWidth)})`,
|
||||
width: options.responsive ? '100%' : `min(100%, ${remFromPx(maxAllowedWidth)})`,
|
||||
...(options.aspectRatio && {
|
||||
aspectRatio: `${scaledDimensions.width}/${scaledDimensions.height}`,
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
const scaleByWidth = maxWidth / dimensions.width;
|
||||
const scaleByHeight = maxHeight / dimensions.height;
|
||||
scale = Math.min(1, scaleByWidth, scaleByHeight);
|
||||
scaledDimensions = {
|
||||
width: Math.max(1, Math.round(dimensions.width * scale)),
|
||||
height: Math.max(1, Math.round(dimensions.height * scale)),
|
||||
};
|
||||
style = {
|
||||
maxWidth: `min(100%, ${remFromPx(maxWidth)})`,
|
||||
width: options.responsive ? '100%' : `min(100%, ${remFromPx(scaledDimensions.width)})`,
|
||||
...(options.aspectRatio && {
|
||||
aspectRatio: `${scaledDimensions.width}/${scaledDimensions.height}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {style, dimensions: scaledDimensions, scale};
|
||||
}
|
||||
|
||||
private normalizeDimensions(dimensions: MediaDimensions): MediaDimensions {
|
||||
return {
|
||||
width: this.normalizeLimit(dimensions.width, 1),
|
||||
@@ -157,9 +147,8 @@ export class MediaDimensionCalculator {
|
||||
maxWidth = DEFAULT_OPTIONS.maxWidth,
|
||||
maxHeight = DEFAULT_OPTIONS.maxHeight,
|
||||
): [number, number] {
|
||||
const calculator = new MediaDimensionCalculator();
|
||||
const result = calculator.calculate({width, height}, {maxWidth, maxHeight, forceScale: true});
|
||||
return [result.dimensions.width, result.dimensions.height];
|
||||
const fitted = fitMediaWithinBounds({width, height, maxWidth, maxHeight});
|
||||
return [fitted.width, fitted.height];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user