for a program I need to show inside a container a document (not the HTML document element) and this document rect as a fixed width and height and should allow zooming from the user. For me making this is very hard and I need help to create it.
This feature I want to make is the same as how MS Word and Libreoffice Writer scales the document. Here I've reverse engineered how it works:
I'm using the preact framework to make this app so I'll show the code of the component.
My first attempt I've tried to use CSS only with translate, but the horizontal scrollbar doesn't show the correct amount to scroll and half the document is not visible.
So I've attempted to add javascript for the calculation and I come up with this component:
import { createRef } from "preact";
import { useLayoutEffect, useState } from "preact/hooks";
import type { JSX } from "preact/jsx-runtime";
export interface ContainerScrollScalableProps
{
width: number,
height: number,
scale: number,
children?: any,
}
export const ContainerScrollScalable = (props: ContainerScrollScalableProps): JSX.Element =>
{
const container = createRef<HTMLDivElement>();
const [containerW, setW] = useState<number>(0);
const [containerH, setH] = useState<number>(0);
useLayoutEffect(() =>
{
if (container.current)
{
setW(container.current.clientWidth);
setH(container.current.clientHeight);
console.log(container.current.scrollTop);
}
}, [container.current]);
const padTop = containerH * 0.2;
const padBottom = containerH * 0.4;
const padXs = 16 * props.scale;
const scaledW = (props.width * props.scale) + (padXs * 2);
const sizeW = containerW > scaledW ? containerW : scaledW;
return (
<div class="w-100 h-100 overflow-y-scroll overflow-x-auto d-flex" ref={container} >
<div class="border" style={"transform-origin: top center;"
+ `min-width: ${sizeW.toFixed(0)}px; min-height: ${props.height}px;`} >
<div style={`width: ${props.width}px; height: ${props.width}px;`
+ "transform-origin: top center;"
+ `transform: translate(${((sizeW / 2) - (props.width / 2) + padXs).toFixed(0)}px, ${(padTop).toFixed(0)}px) scale(${props.scale});`}
children={props.children} />
</div>
</div>
);
}
Then the component is used like this, will show a border so it doesn't need actual children elements to test it:
const zoom = getUserZoom(); // range 0.1 --> 3.0
// ...
<ContainerScrollScalable width={1080} height={1920} scale={zoom} />
This component has some problems:
How could I allow this component to properly scale his content and allow the scrollbar position to follow the scale?
Makes proper scaling and centering with padding. Dynamic Sizing added Resizing Support adapts to window size.
import { createRef } from "preact";
import { useLayoutEffect, useState } from "preact/hooks";
import type { JSX } from "preact/jsx-runtime";
export interface ScalableProps {
initialWidth: number;
initialHeight: number;
scale: number;
children?: any;
}
export const ScalableContainer = (props: ScalableProps): JSX.Element => {
const containerRef = createRef<HTMLDivElement>();
const [containerDims, setDims] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
const update = () => {
if (containerRef.current)
setDims({
width: containerRef.current.clientWidth,
height: containerRef.current.clientHeight,
});
};
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
const scaledW = props.initialWidth * props.scale;
const scaledH = props.initialHeight * props.scale;
const padding = 16;
return (
<div
ref={containerRef}
class="w-100 h-100 overflow-scroll"
style={{ position: "relative" }}
>
<div
style={{
width: `${scaledW + padding * 2}px`,
height: `${scaledH + padding * 2}px`,
transform: `scale(${props.scale})`,
transformOrigin: "top center",
padding,
}}
>
<div
style={{
width: `${props.initialWidth}px`,
height: `${props.initialHeight}px`,
border: "1px solid #ccc",
background: "#f0f0f0",
}}
>
{props.children}
</div>
</div>
</div>
);
};
Example usage:
<ScalableContainer initialWidth={1080} initialHeight={1920} scale={1.5} />