Its been a year since I started learning to code, All this while I have been building bizmo.site, a drag-and-drop website builder. A hurdle that constantly kept me bothering was with the builder screen, On my laptop it looked perfect. However, on my friend's laptop, there were layout issues and blank spaces. Since Bizmo uses a canvas to show a scaled down version of the website so the aspect ratio matches the final output, I needed a way to dynamically adjust it for different desktop screen sizes. Exploring for solutions, going through all kinds of online forums only thing i got was to use media queries with breakpoints which seemed too rigid. They wouldn't adapt until a specific screen size was reached, and I'd need a ton of breakpoints for every pixel size.
So, I decided to explore a dynamic scaling system that adjusts automatically based on the user's viewport width. This would ensure a seamless user experience regardless of screen size.
import { useState, useEffect } from 'react';
const useDynamicScale = () => {
const [scale, setScale] = useState(1);
const calculateScale = () => {
const viewportWidth = window.innerWidth;
const referencePoints = [
{ width: 1263, scale: 0.6 },
{ width: 1519, scale: 0.66 },
{ width: 1903, scale: 0.72 },
];
referencePoints.sort((a, b) => a.width - b.width);
let lowerBound = referencePoints[0];
let upperBound = referencePoints[referencePoints.length - 1];
for (let i = 0; i < referencePoints.length - 1; i++) {
if (
viewportWidth >= referencePoints[i].width &&
viewportWidth < referencePoints[i + 1].width
) {
lowerBound = referencePoints[i];
upperBound = referencePoints[i + 1];
break;
}
}
const widthRatio =
(viewportWidth - lowerBound.width) /
(upperBound.width - lowerBound.width);
const calculatedScale =
lowerBound.scale + widthRatio * (upperBound.scale - lowerBound.scale);
return Math.max(0.5, Math.min(1, calculatedScale));
};
useEffect(() => {
const handleResize = () => {
setScale(calculateScale());
};
handleResize(); // Set initial scale
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return scale;
};
export default useDynamicScale;
Whats Going On ?
The calculateScale
function figures out the right scale based on the viewport width. I’ve got some reference points with widths and corresponding scales. The function finds the right bounds and interpolates the scale value accordingly to calculate scale values for dynamic viewports.
Building the package -
Now that we've developed a dynamic scaling solution using React, it's time to turn this into a reusable package that can be easily integrated into various projects. Here’s how you can build and package.
I'am assuming you already know basics of React, Js/Ts, npm and node
Create a package.json
file using the command:
npm init
This is how my package.json looks like with all dependencies necessary
{
"name": "vw-scale-calculator",
"version": "2.0.5",
"description": "Calculate scale based on viewport width and reference points",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"keywords": ["viewport", "scale", "calculator", "responsive"],
"author": "Sreeragh",
"license": "MIT",
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
"typescript": "^4.0.0",
"@types/react": "^17.0.0"
}
}
Follow the prompts to fill out the details. Make sure to set the main
field to src/index.js
and add any necessary dependencies.
src/index.js
export type ReferencePoint = {
width: number;
scale: number;
}
export type ScaleCalculatorOptions = {
referencePoints: ReferencePoint[];
minScale?: number;
maxScale?: number;
}
export class VWScaleCalculator {
private referencePoints: ReferencePoint[];
private minScale: number;
private maxScale: number;
constructor(options: ScaleCalculatorOptions) {
this.referencePoints = options.referencePoints.sort((a, b) => a.width - b.width);
this.minScale = options.minScale ?? 0.5;
this.maxScale = options.maxScale ?? 1.0;
}
calculateScale(viewportWidth: number): number {
let lowerBound = this.referencePoints[0];
let upperBound = this.referencePoints[this.referencePoints.length - 1];
for (let i = 0; i < this.referencePoints.length - 1; i++) {
if (
viewportWidth >= this.referencePoints[i].width &&
viewportWidth < this.referencePoints[i + 1].width
) {
lowerBound = this.referencePoints[i];
upperBound = this.referencePoints[i + 1];
break;
}
}
const widthRatio =
(viewportWidth - lowerBound.width) /
(upperBound.width - lowerBound.width);
const calculatedScale =
lowerBound.scale + widthRatio * (upperBound.scale - lowerBound.scale);
return Math.max(this.minScale, Math.min(this.maxScale, calculatedScale));
}
onResize(callback: (scale: number) => void): () => void {
const handleResize = () => {
const scale = this.calculateScale(window.innerWidth);
callback(scale);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
}
and tsconfig.json to make your package typescript compatible.
tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"outDir": "./dist",
"declaration": true,
"declarationDir": "./dist",
"lib": ["ES2015", "DOM"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
use the below command to install all packages
npm i
Command to run tsc script
npm run build
login to npm
npm login
publish package
npm publish
conclusion
It was really exciting to learn and publish my first npm package, please tweet at me when yours is live! 😊
Happy Coding!