Skip to main content

Command Palette

Search for a command to run...

How i Implemented a Flexible Dimension Control in Open Quran View v0.5.0

Published
7 min read
A
I am a web developer

How we solved the aspect ratio constraint problem and created a more flexible rendering system for Quran pages.


Live Demo: http://mubin.adelpro.us.kg

The Problem with Fixed Ratios

When Open Quran View first launched, it used a fixed 0.7 mushaf ratio (width/height) to derive missing dimensions. This worked perfectly when your container matched the standard mushaf proportions:


// Default behavior - derives height from width using 0.7 ratio

<OpenQuranView width={600} />

// Results in: width=600, height=857 (600 / 0.7)

But real-world applications have varied container sizes. A mobile app might have a short, wide container. A website might have a tall, narrow sidebar. A fullscreen viewer might want maximum width at the expense of height.

The old system couldn't handle these cases elegantly:

| Container Shape | Old Behavior |

|-----------------|--------------|

| Wider than 0.7 ratio | Text overflowed horizontally |

| Taller than 0.7 ratio | Wasted space, text too small |

| Neither dim provided | Only width tracked, height derived |

The Font Size Problem

The layout calculator derived font size from height:


const computedLineHeight = availableHeight / 15;

const fontSize = lineHeight / 1.5;

This meant text width depended solely on chars * fontSize * 0.5. When height was artificially large (like a tall, narrow container), the font grew accordingly—but the line width didn't, causing words to overflow and overlap.


The Solution: Two New Props

1. The ratio Prop

Control whether dimension derivation happens at all:


// Default - use mushaf ratio (0.7) to derive missing dimension

<OpenQuranView ratio={true} />

// Disable derivation - fill container exactly

<OpenQuranView ratio={false} />

// Custom ratio value

<OpenQuranView ratio={0.8} />

| Value | Behavior |

|-------|----------|

| true (default) | Uses 0.7 mushaf ratio to derive missing dimension |

| false | No derivation; uses explicit dims or container measurements |

| number | Custom ratio (e.g., 0.8, 1.0) overrides default |

2. The fit Prop

Control which dimension fills the container when neither is provided:


// Default - fill width, derive height

<OpenQuranView fit="width" />

// Fill height, derive width

<OpenQuranView fit="height" />

| Value | Behavior |

|-------|----------|

| "width" (default) | ResizeObserver tracks container width; height derived |

| "height" | ResizeObserver tracks container height; width derived |


Real-World Use Cases

Fullscreen Wide Viewing


// User wants maximum width for immersive reading

<OpenQuranView

fit="width"

ratio={true}

className="fullscreen-viewer"

/>

The component fills the screen width, and height follows naturally. User scrolls vertically to read.

Horizontal Scroll for Wide Screens


// Dashboard with wide container - scroll sideways

<OpenQuranView

fit="height"

ratio={true}

style={{ height: '100vh' }}

/>

The component fills the full height, and width expands accordingly. User scrolls horizontally.

Fixed-Size Widget


// Fixed dimensions - no derivation

<OpenQuranView

width={300}

height={400}

ratio={false}

/>

The component uses exactly 300x400, regardless of mushaf proportions.


The Font Size Fix

The Core Issue

When width and height don't match the mushaf ratio, font calculation broke down:


Container: 800px wide × 600px tall

Ratio: 1.33 (width/height)

Old calculation:

- lineHeight = 600 / 15 = 40px
- fontSize = 40 / 1.5 = 26.7px
- Line width = ~600px (text fits)

But derived height = 800 / 0.7 = 1143px

Font becomes too large for the actual container!

The min() Solution

We now calculate font size from both constraints and use the minimum:


// Calculate font size from height (original approach)

const fontSizeFromHeight = lineHeight / 1.5;

// Calculate font size from width constraint

// ~80 chars per line is a safe estimate for Arabic text

const availableWidth = pageWidth - paddingLeft - paddingRight;

const avgCharsPerLine = 80;

const fontSizeFromWidth = availableWidth / (avgCharsPerLine * 0.5);

// Use the minimum to ensure text fits both constraints

const fontSize = Math.min(fontSizeFromHeight, fontSizeFromWidth);

This ensures:

  • Text never overflows horizontally

  • Font is as large as possible within both constraints

  • The 15-line Al-Madinah Mushaf standard is preserved

Mathematical Proof

For a container W × H:


fontSizeFromHeight = (H × 0.9) / 15 / 1.5 = H / 25

fontSizeFromWidth = W / 40  (assuming 80 chars, 0.5 multiplier)

Text fits when: fontSize ≤ fontSizeFromWidth

fontSize ≤ fontSizeFromHeight

Therefore: fontSize ≤ min(W / 40, H / 25)

Prop Validation

To help developers avoid confusion, the component emits development-only warnings when props conflict:


// Both dimensions + fit - fit is ignored

<OpenQuranView width={600} height={850} fit="height" />

// Warning: "Both width and height are provided. The fit prop will be ignored."

// fit="height" with explicit width - fit is ignored

<OpenQuranView width={600} fit="height" />

// Warning: "fit="height" is ignored when width is provided without height"

// ratio=false with fit - fit is ignored

<OpenQuranView ratio={false} fit="height" />

// Warning: "fit is ignored when ratio={false}"

// Fullscreen with explicit dims - dims ignored

<OpenQuranView fullscreen width={600} />

// Warning: "Explicit width/height are ignored in fullscreen mode"

These warnings only appear in development (process.env.NODE_ENV !== 'production') and are stripped from production bundles.


Migration Guide

v0.4.x → v0.5.0

  • No breaking changes.* The new props have safe defaults that match v0.4.x behavior.

// Before (v0.4.x)

<OpenQuranView width={600} />

// After (v0.5.0) - identical behavior

<OpenQuranView width={600} />

New Capabilities


// Disable ratio derivation entirely

<OpenQuranView ratio={false} />

// Use custom ratio

<OpenQuranView ratio={0.75} />

// Fill height instead of width

<OpenQuranView fit="height" />

Behavior Comparison

| Props | v0.4.x | v0.5.0 |

|-------|--------|--------|

| width={600} | width=600, height derived | Same |

| height={800} | height=800, width derived | Same |

| ratio={false} | N/A | No derivation |

| fit="height" | N/A | Fill height instead |

| ratio={0.8} | N/A | Custom ratio |


Under the Hood

ResizeObserver Changes

The first useEffect now tracks dimensions based on fit:


const resizeObserver = new ResizeObserver((entries) => {

for (const entry of entries) {

const rect = entry.contentRect;

if (fit === "height") {

if (rect.height > 0) setContainerHeight(rect.height);

} else {

if (rect.width > 0) setContainerWidth(rect.width);

}

}

});

Derivation Logic

The second useEffect handles dimension derivation:


const actualRatio = typeof ratio === "number" ? ratio : MUSHAF_RATIO;

if (height && !isFullscreen) {

const derivedWidth = height * actualRatio;

setContainerWidth(derivedWidth);

setContainerHeight(height);

return;

}

if (containerWidth > 0) {

setContainerHeight(containerWidth / actualRatio);

}

When ratio={false}, this entire effect is skipped.

Layout Calculator Integration

The createLayoutCalculator function now uses the min approach:


const fontSizeFromHeight = lineHeight / 1.5;

const fontSizeFromWidth = availableWidth / (avgCharsPerLine * 0.5);

const fontSize = options.fontSize || Math.min(fontSizeFromHeight, fontSizeFromWidth);

API Reference

React Component


import { OpenQuranView } from '@open-quran-view/view';

<OpenQuranView

page={1}

width={600}

height={850}

ratio={true}

fit="width"

theme="light"

mushafLayout="hafs-v2"

onPageChange={(page) => console.log(page)}

onWordClick={(word) => console.log(word)}

/>

Web Component


<open-quran-view

page="1"

width="600"

height="850"

ratio="true"

fit="width"

theme="light"

mushaf-layout="hafs-v2"

></open-quran-view>

Prop Details

| Prop | Type | Default | Description |

|------|------|---------|-------------|

| ratio | boolean \| number | true | Aspect ratio control |

| fit | "width" \| "height" | "width" | Which dim to fill |


Performance Considerations

The new props don't add runtime overhead:

  • ratio and fit are just conditionals in existing effects

  • Font calculation still happens once per page render

  • ResizeObserver usage remains the same

Minification strips dev warnings from production:


// Development

if (process.env.NODE_ENV !== "production") {

console.warn("...");

}

// Production (UglifyJS/Terser drops unreachable code)

if (false) {

console.warn("...");

}

Result: zero warnings in production, full guidance in development.


Conclusion

Open Quran View v0.5.0 brings flexible dimension control without breaking existing behavior. The new ratio and fit props let developers create diverse layouts while the font size fix ensures text never overflows.

The key insight: constraints should inform, not limit. By calculating font size from both width and height constraints, we created a system that adapts to any container shape while maintaining the beautiful mushaf typography.

Try it out:


npm install open-quran-view@latest

  • Jazakum Allahu Khairan for reading. May this serve the Muslim ummah in accessing the Quran beautifully on any device.*