简体   繁体   中英

Make HTML element scale down or up with browser while keeping aspect ratio

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

I would use this logic upon calculation:

  • Use a responsive centering trick using responsive aspect-ratio from this related answer
  • Use 3mm safe padding on the A4 sheet
  • Calculate the rows and cols given one cell's size (W/H in mm ) - not forgetting the with spacing needed (2mm border and spacing on both sides of a cell! )
  • Determine the tot cells
  • Once you create the N*N grid, all you need to do is calculate only the width of a cell in percent, and set to that cell the needed aspect ratio
  • Don't forget that the grid's gap and spacing need to be calculated and applied also in percentage % values — in order to be responsive

Example:

 // DOM utility functions: const el = (sel, par) => (par || document).querySelector(sel); const els = (sel, par) => (par || document).querySelectorAll(sel); const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop); const css = (el, styles) => typeof styles === "object"? Object.assign(el.style, styles): el.style.cssText = styles; // Utility functions: const repeat = (n, cb) => [...Array(n)].forEach((_, i) => cb(i)) const toMM = (n = 0, unit = "in") => ({in: n * 25.4, cm: n * 10, px: n / 3.7795275591}[unit.toLowerCase()]?? n); // App: const elSheet = el("#sheet"); const elCount = el("#count"); const elsUnit = els("[name='unit']"); const elsUnitSymb = els(".unit"); const elWidth = el("#width"); const elHeight = el("#height"); // Default values in "mm" units: const sheetWidthMM = 210; const sheetHeightMM = 297; const borderMM = 2; const spacingMM = 3; const displayStickers = () => { const unit = [...elsUnit].filter(elUnit => elUnit.checked)[0].value?? "mm"; const widthMM = toMM(Number(elWidth.value)?? 0, unit); const heightMM = toMM(Number(elHeight.value)?? 0, unit); const rows = Math.floor((sheetHeightMM - spacingMM * 2) / (heightMM + borderMM * 2 + spacingMM)); const cols = Math.floor((sheetWidthMM - spacingMM * 2) / (widthMM + borderMM * 2 + spacingMM)); const totalStickers = rows * cols; const spacingPct = spacingMM / sheetWidthMM * 100; const gridWidthMM = (sheetWidthMM - spacingMM * 2); const colWidthMM = gridWidthMM / cols; const stickerWidthPct = widthMM / colWidthMM * 100; const stickerRatio = widthMM / heightMM; elSheet.style.setProperty("--stickerWidth", stickerWidthPct); elSheet.style.setProperty("--stickerRatio", stickerRatio); css(elSheet, { gridTemplateColumns: `repeat(${cols}, ${1}fr)`, gridTemplateRows: `repeat(${rows}, ${1}fr)`, gap: `${spacingPct}%`, padding: `${spacingPct}%`, }); elSheet.innerHTML = ""; repeat(totalStickers, () => elSheet.append(elNew("div", {className: "sticker"}))); elCount.textContent = totalStickers; elsUnitSymb.forEach(elUnitSymb => elUnitSymb.textContent = unit); }; [...elsUnit, elWidth, elHeight].forEach((elInp) => elInp.addEventListener("input", displayStickers)); displayStickers();
 @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap'); * { margin: 0; box-sizing: border-box; } html, body { height: 100vh; } p { padding: 0.2rem 0; } main { display: flex; width: 100%; height: 100%; } #panel { background: #eee; padding: 2rem; display: flex; flex-direction: column; min-width: 240px; width: 20%; justify-content: center; } #panel input[type="text"] { width: 50px; } #area { flex: 1; display: flex; padding: 2rem; position: relative; } #sheet { font-size: 1mm; position: absolute; display: grid; background: #fff; margin: auto; left: 0; right: 0; top: 0; bottom: 0; max-width: calc(100% - 3rem); max-height: calc(100% - 3rem); /* Vertical A4 aspect ratio: */ aspect-ratio: 0.707070707070707; overflow: hidden; box-shadow: 0 0.3rem 2rem 0 rgba(0 0 0 / 0.3); }.sticker { background: #e84f1dad; margin: auto; width: calc(var(--stickerWidth) * 1%); aspect-ratio: var(--stickerRatio); }
 <main> <section id="panel"> <p> Enter the desired sticker dimensions: </p> <p id="units"> <label><input type="radio" name="unit" value="mm" checked> mm</label> <label><input type="radio" name="unit" value="cm"> cm</label> <label><input type="radio" name="unit" value="in"> in</label> </p> <p> <label>Width <input id="width" type="text" value="60"><span class="unit">mm</span></label> <br> <label>Height <input id="height" type="text" value="60"><span class="unit">mm</span></label> </p> <p> At these dimensions we can fit <b id="count">48</b> stickers per sheet </p> <button type="button">Order here</button> </section> <section id="area"> <div id="sheet"></div> </section> </main>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM