I sell stickers on A4 sheets, I get a lot of customers asking how many stickers can I get at "specific dimensions". I am making a page where you can enter length and width of a sticker and it will calculate how many stickers of that length and width can fit on the A4 sheet. The sheet is 210x297mm. I always have to allow a 2mm border on all designs and a 2mm space between each logo. Everything is working fine. Except the part when you resize you window height. The sheet with not stay the same. I want the sheet width and height to scale together.
I don't know how to use transform scale in this case so I tried just changing the height and width to make it scale together. So the height scales, but the width will stay where it is until you actually change the width as if on mobile. You'll see what I mean once you preview the HTML and resize the browser.
Here is a snippet of the troublesome part in the resize function where I'm struggling from.
function resize() {
setTimeout(() => {
sheet: {
sheetWidthPX = inToPX(sheetWidth), newWidth = sheetWidthPX * (document.querySelector('.right').clientWidth / sheetWidthPX) - 60;
sheetHeightPX = inToPX(sheetHeight), newHeight = sheetHeightPX * (document.documentElement.clientHeight / sheetHeightPX) - 60; // 60px extra space
// stickersContainer.style.setProperty('--stickersWidth', newWidth + 'px');
stickersContainer.style.setProperty('--stickersWidth', '555px');
stickersContainer.style.setProperty('--stickersHeight', newHeight + 'px');
}
stickers: {
logo = stickersContainer.querySelector('.logo');
firstSticker = stickers.querySelector('.sticker');
parentWidth = stickersContainer.offsetWidth;
parentHeight = stickersContainer.offsetHeight - logo.offsetHeight;
width = firstSticker.offsetWidth, height = firstSticker.offsetHeight;
// newWidth = stickers-width * (sheet-width / (stickers-width * l)) - spacings * (how many gaps)
newWidth = width * (parentWidth / (width * stickersPerRow)) - inToPX(spacing) * 2;
newHeight = height * (parentHeight / (height * stickersPerCol)) - inToPX(spacing) * 2;
stickersContainer.style.setProperty('--stickerWidth', newWidth + 'px');
stickersContainer.style.setProperty('--stickerHeight', newHeight + 'px');
}
}, 0);
}
My brain is fried with the math. Perhaps there is a better way of doing everything? If not, maybe I should consider learning canvas?
Here is the full JavaScript, CSS, and HTML
addEventListener('DOMContentLoaded', () => {
const stickerWidth = 60;
const stickerHeight = 40;
const sheetWidth = 210;
const sheetHeight = 297;
const border = 2;
const spacing = 3;
const stickersContainer = document.querySelector('.stickersContainer');
const stickers = stickersContainer.querySelector('.stickers');
const inToPX = n => n * 3.7795275591; // default unit is mm
function toMM(n, unit) {
switch (unit || (document.querySelector('input[name="unit"]:checked')?.value || 'mm').toLowerCase()) {
case "in": return n *= 25.4;
case "cm": return n *= 10;
case "px": return n /= 3.7795275591;
default: return n;
}
}
function displayStickers(length, width, unit = 'mm') {
length = toMM(length, unit);
width = toMM(width, unit);
stickersContainer.style.setProperty('--opacity', 0);
// Calculate the number of stickers that can fit on the sheet in each direction
stickersPerRow = Math.floor((sheetWidth - border) / (length + spacing));
stickersPerCol = Math.floor((sheetHeight - border) / (width + spacing));
// Calculate the total number of stickers that can fit on the sheet
const totalStickers = stickersPerRow * stickersPerCol;
stickers.style.gridTemplateColumns = `repeat(${stickersPerRow}, ${length}fr)`;
// stickers.style.gridTemplateRows = `repeat(${stickersPerCol}, ${width}mm)`;
stickers.style.gap = `${spacing}mm`;
stickers.style.pdding = `${spacing}mm`;
stickers.innerHTML = '';
resize();
stickersContainer.style.setProperty('--stickerWidth', length + 'mm');
stickersContainer.style.setProperty('--stickerHeight', width + 'mm');
setTimeout(() => stickersContainer.style.removeProperty('--opacity'), 0);
for (let i = 0; i < totalStickers; i++) {
const sticker = document.createElement('div');
sticker.classList.add('sticker');
stickers.appendChild(sticker);
}
document.querySelector('.count').textContent = totalStickers;
}
function resize() {
setTimeout(() => {
sheet: {
sheetWidthPX = inToPX(sheetWidth), newWidth = sheetWidthPX * (document.querySelector('.right').clientWidth / sheetWidthPX) - 60;
sheetHeightPX = inToPX(sheetHeight), newHeight = sheetHeightPX * (document.documentElement.clientHeight / sheetHeightPX) - 60; // 60px extra space
// stickersContainer.style.setProperty('--stickersWidth', newWidth + 'px');
stickersContainer.style.setProperty('--stickersWidth', '555px');
stickersContainer.style.setProperty('--stickersHeight', newHeight + 'px');
}
stickers: {
logo = stickersContainer.querySelector('.logo');
firstSticker = stickers.querySelector('.sticker');
parentWidth = stickersContainer.offsetWidth;
parentHeight = stickersContainer.offsetHeight - logo.offsetHeight;
width = firstSticker.offsetWidth, height = firstSticker.offsetHeight;
// newWidth = stickers-width * (sheet-width / (stickers-width * l)) - spacings * (how many gaps)
newWidth = width * (parentWidth / (width * stickersPerRow)) - inToPX(spacing) * 2;
newHeight = height * (parentHeight / (height * stickersPerCol)) - inToPX(spacing) * 2;
stickersContainer.style.setProperty('--stickerWidth', newWidth + 'px');
stickersContainer.style.setProperty('--stickerHeight', newHeight + 'px');
}
}, 0);
}
const editableElements = document.querySelectorAll('[contenteditable]');
const unitElements = document.querySelectorAll('[contenteditable]+.unitText');
for (const unitText of unitElements) {
const editableElement = unitText.previousElementSibling;
unitText.addEventListener('click', e => {
editableElement.focus();
range = document.createRange();
range.selectNodeContents(editableElement);
range.collapse(false);
selection = getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
editableElement.addEventListener('keydown', e => {
if (e.key == 'Enter' || !(e.key.length > 1 || !/[^.\d]/g.exec(e.key)))
e.preventDefault();
oldUnit = document.querySelector('input[name="unit"]:checked').value;
if (editableElement == unitElements[0].previousElementSibling)
oldWidth = parseFloat(editableElement.textContent.replace(/[^.\d]/g, ''));
else if (editableElement == unitElements[1].previousElementSibling)
oldHeight = parseFloat(editableElement.textContent.replace(/[^.\d]/g, ''));
});
editableElement.addEventListener('input', e => {
const newValue = parseFloat(e.target.textContent.replace(/[^.\d]/g, '') + e.key);
const unit = document.querySelector('input[name="unit"]:checked').value;
if ((!newValue && newValue != 0) || newValue < 0) {
e.target.textContent = 0;
e.target.focus();
return document.execCommand('selectAll', false, null);
}
if (e.target == unitElements[0].previousElementSibling && toMM(newValue) > sheetWidth) {
e.target.contentEditable = false;
var msg = `The width of the sheet is ${sheetWidth}mm. A ${newValue}${unit}${unit !== 'mm' ? ` (or ${toMM(newValue, unit)}mm) ` : ' '}width would be above it.`;
document.body.classList.add('calculations-invalid');
} else if (e.target == unitElements[1].previousElementSibling && toMM(newValue) > sheetHeight) {
e.target.contentEditable = false;
var msg = `The height of the sheet is ${sheetHeight}mm. A ${newValue}${unit}${unit !== 'mm' ? ` (or ${toMM(newValue, unit)}mm) ` : ' '}height would be above it.`;
document.body.classList.add('calculations-invalid');
} else {
e.target.contentEditable = true;
msg = false;
}
if (!msg)
document.body.classList.remove('calculations-invalid', 'invalid');
else {
setTimeout(() => {
e.target.contentEditable = true;
e.target.focus();
if (!document.body.classList.contains('invalid')) {
alert(msg);
document.execCommand('selectAll', false, null);
document.body.classList.add('invalid');
}
}, 0);
}
});
}
oldUnit = document.querySelector('input[name="unit"]:checked').value;
oldWidth = parseFloat(editableElements[0].textContent.replace(/[^.\d]/g, ''));
oldHeight = parseFloat(editableElements[1].textContent.replace(/[^.\d]/g, ''));
for (const checkbox of document.querySelectorAll('input[name="unit"]'))
checkbox.addEventListener('change', e => {
const unit = e.target.value
for (const unitText of document.querySelectorAll('.unitText'))
unitText.textContent = unit;
});
function calculate() {
let [width, height] = document.querySelectorAll('[role="textbox"][contenteditable]');
let unit = document.querySelector('input[name="unit"]:checked').value;
width = parseFloat(width.textContent.replace(/[^.\d]/g, ''));
height = parseFloat(height.textContent.replace(/[^.\d]/g, ''));
if (toMM(width) > sheetWidth)
return alert(`The width of the sheet is ${sheetWidth}mm. ${width}${unit} ${unit !== 'mm' ? `(or ${toMM(width, unit)}mm)` : ''} is too much.`);
if (toMM(height) > sheetHeight)
return alert(`The height of the sheet is ${sheetHeight}mm. ${height}${unit} ${unit !== 'mm' ? `(or ${toMM(height, unit)}mm)` : ''} is too much.`);
console.log(width, height, unit);
displayStickers(width, height, unit);
}
addEventListener('resize', resize);
displayStickers(stickerWidth, stickerHeight, 'mm');
document.querySelector('button.calculate').addEventListener('click', calculate);
document.querySelector('input[name="unit"]:checked')?.dispatchEvent(new Event('change'));
});
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap');
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
font-size: 16px;
color: var(#333);
height: 100vh;
font-family: 'Roboto', sans-serif;
}
main {
/* display: grid; */
/* grid-template-columns: 1fr 1fr; */
gap: 20px;
display: flex;
justify-content: space-around;
justify-content: space-evenly;
padding: 20px;
height: 100vh;
}
.left {
display: grid;
place-content: center;
text-align: center;
}
section.left :is(button, a) {
all: unset;
background: #e84e1c;
color: #fff;
border-radius: 4px;
padding: 3px 10px;
cursor: pointer;
}
section.left button.calculate {
padding: 2px 10px;
margin: 6% 0;
}
section.left a {
padding: 3px 2px;
}
body[class$=invalid] section.left button.calculate {
opacity: .5;
cursor: not-allowed;
pointer-events: none;
}
.left>.leftContent {
transform: translateY(-5%);
}
.stickersContainer {
width: var(--stickersWidth, 205mm);
height: var(--stickersHeight, 260mm);
/* width: 40vw; */
/* height: 90vh; */
margin: 0 auto;
display: grid;
opacity: var(--opacity, 1);
/* place-content: center; */
--border-radius: 10px;
border-radius: var(--border-radius);
border: 1px solid #ddd;
grid-template-rows: 8%;
/* padding: 0 6mm 6mm 6mm; */
/* padding: 0 6mm 8% 6mm; */
padding: 0 6mm 0mm 6mm;
}
.stickers {
padding-bottom: 8%;
}
.stickersContainer>.logo {
display: grid;
}
.stickersContainer>.logo img {
width: 17%;
place-self: center;
align-self: center;
}
@-moz-document url-prefix() {
.stickersContainer>.logo img {
margin: 25px 0;
}
.stickers {
padding-bottom: 3.5%;
}
}
.stickers {
display: grid;
place-content: center;
place-items: center;
}
.stickers .sticker {
border: 2px solid #e84f1d;
border: 2px solid #e84f1dab;
border-radius: var(--border-radius);
width: var(--stickerWidth, 60mm);
height: var(--stickerHeight, 40mm);
}
.value {
border: 1px solid #7e7e7c;
display: inline-flex;
border-radius: 6px;
margin-left: 3px;
padding: 1.6px 8px;
font-size: 80%;
}
.value .input {
display: inline-table;
border-radius: 3px;
margin-left: 5px;
margin: 0;
}
.value .input::after {
content: attr(data-after);
opacity: .5;
}
.value .input:empty {
width: 20px;
}
[role="textbox"]:focus-within {
outline: none;
}
.messures {
display: grid;
gap: 10px;
font-size: 120%;
}
.messures .width {
margin-left: 8px;
}
.left .units {
margin: 1% 0 10% 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
@media screen and (max-width: 960px) {
main {
display: block;
}
main .left {
margin: 20px 0 50px 0;
}
}
@media screen and (max-width: 600px) {
main .stickersContainer {
width: 100%;
}
}
[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<main>
<section class="left">
<div class="leftContent">
<div>Please enter the dimentions of your shape</div>
<div class="units">
<span><input type="radio" name="unit" value="mm" checked autocomplete="off">MM</span>
<span><input type="radio" name="unit" value="cm" autocomplete="off">CM</span>
<span><input type="radio" name="unit" value="in" autocomplete="off">Inches</span>
</div>
<div class="messures">
<div>
<label for="width">Width <span>(</span><span class="unitText">mm</span>)</label>
<span class="value">
<span class="input" role="textbox" contenteditable>60</span>
<span class="unitText">mm</span>
</span>
</div>
<div>
<label for="height">Height (<span class="unitText">mm</span>)</label>
<span class="value">
<span class="input" role="textbox" contenteditable>40</span>
<span class="unitText">mm</span>
</span>
</div>
</div>
<button class="calculate">Calculate</button>
<div>At these dimentions we can fit</div>
<div><span class="count"></span> stickers per sheet</div>
<div style="margin-top: 9%;">To order click <a href="">here</a></div>
</div>
</section>
<section class="right">
<div class="rightContent">
<div class="stickersContainer">
<div class="logo">
<img class="regular-logo"
src="img.jpg"
alt="Stickers On Sheet">
</div>
<div class="stickers"></div>
</div>
</div>
</section>
</main>
</body>
</html>
You might understand the problem better if you open them manually than through StackOverflow as it makes a small window