Pixel to Rem converter

Why make this tool?

As a frontend developer I often need to bounce back and forth using rems and pixels regularly. Sure there are nice websites that can do this. You can use preprocessor tools like sass mixins. Browser devtools have also introduced some handy new was to do this.

javascript
const bookmarklet = { name: "bookmakrlet__pixel-to-rem", version: "1.6" };

try {
  // Create unique names
  const component = {
    wrapper: `${bookmarklet.name}__wrapper`,
    close: `${bookmarklet.name}__close`,
    rootLabel: `${bookmarklet.name}__root-label`,
    rootInput: `${bookmarklet.name}__root-input`,
    pixelLabel: `${bookmarklet.name}__pixel-label`,
    pixelInput: `${bookmarklet.name}__pixel-input`,
    pixelCopy: `${bookmarklet.name}}__pixel-copy`,
    remLabel: `${bookmarklet.name}__rem-label`,
    remInput: `${bookmarklet.name}__rem-input`,
    remCopy: `${bookmarklet.name}__rem-copy`
  };

  // Helper functions
  const createElement = (type, className, attributes = {}) => {
    const element = document.createElement(type);
    element.className = className;
    Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, value));
    return element;
  };

  const copyText = text => {
    try { navigator.clipboard.writeText(text); } 
    catch (err) { console.error("Failed to copy:", err); }
  };

  // Create container
  const container = createElement("div", component.wrapper);
  document.body.appendChild(container);

  // Create close button
  const close = createElement("button", component.close, { type: "button" });
  close.textContent = "Close";
  close.onclick = () => container.remove();
  container.appendChild(close);

  // SVG icon
  const copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/></svg>';

  // Create input groups
  const createInputGroup = (labelText, inputId, copyId, copyTitle) => {
    const label = createElement("label", inputId.replace('input', 'label'));
    label.textContent = labelText;
    container.appendChild(label);

    const input = createElement("input", inputId, { type: "number", id: inputId, placeholder: "0" });
    label.appendChild(input);

    if (copyId) {
      const copyBtn = createElement("button", copyId, { type: "button", id: copyId, title: copyTitle });
      copyBtn.innerHTML = copyIcon;
      copyBtn.onclick = () => copyText(`${input.value}${labelText}`);
      label.appendChild(copyBtn);
    }

    return input;
  };

  // Create inputs
  const rootInput = createInputGroup("root", component.rootInput);
  const pixelInput = createInputGroup("px", component.pixelInput, component.pixelCopy, "Copy Pixel");
  const remInput = createInputGroup("rem", component.remInput, component.remCopy, "Copy Rem");

  // Set initial values
  const rootFontSize = window.getComputedStyle(document.documentElement).fontSize.replace('px', '');
  rootInput.value = rootFontSize;
  pixelInput.value = 10;
  remInput.value = pixelInput.valueAsNumber / rootInput.valueAsNumber;

  // Add event listeners
  const updateValues = source => {
    if (source === rootInput || source === pixelInput) {
      remInput.value = pixelInput.valueAsNumber / rootInput.valueAsNumber;
    } else if (source === remInput) {
      pixelInput.value = remInput.valueAsNumber * rootInput.valueAsNumber;
    }
  };

  for (const input of [rootInput, pixelInput, remInput]) {
    input.addEventListener("input", event => updateValues(event.target));
  }

  document.addEventListener("keydown", event => {
    if (event.key === "Escape") container.remove();
  });

  // Add styles
  const colors = {
    blue: '#b9e7de',
    white: '#ffffff',
    grayLight: '#edece8',
    grayMid: '#6b6b6b',
    grayDark: '#333333',
    black: '#161613'
  };

  const style = document.createElement("style");
  style.textContent = `
    .${component.wrapper} {
      width: 100%; letter-spacing: 1px; background-color: ${colors.blue} !important;
      padding: 10px 60px; color: ${colors.black} !important; margin: auto !important;
      position: fixed !important; top: 0 !important; right: 0 !important; left: 0 !important;
      z-index: 2147483647 !important; font-size: 18px !important; font-family: helvetica !important;
      display: flex !important; justify-content: center !important; gap: 2rem !important;
    }
    .${component.close} {
      position: absolute; left: 0 !important; top: 0 !important; background-color: ${colors.grayLight} !important;
      color: ${colors.black} !important; z-index: 30 !important; padding: 8px !important;
      font-size: 18px !important; height: 100% !important; cursor: pointer !important;
      display: flex !important; justify-content: center !important; align-items: center !important;
      border: 0 !important; font-weight: normal !important; border-right: 2px solid ${colors.black} !important;
    }
    .${component.close}:hover { background-color: ${colors.white} !important; }
    .${component.wrapper} label {
      padding: 0 0 0 8px !important; border-top: 2px solid ${colors.grayDark} !important;
      border-left: 2px solid ${colors.grayDark} !important; border-bottom: 2px solid ${colors.grayDark} !important;
      background-color: #eee !important; font-family: monospace !important; letter-spacing: 0.5px !important;
      display: flex !important; align-items: center !important; margin: 0 !important; font-size: inherit !important;
    }
    .${component.wrapper} input[type='number'] {
      padding: 3px 6px !important; border-top: 0 !important; border-bottom: 0;
      border-right: 2px solid ${colors.grayDark} !important; margin: 0 0 0 8px !important;
      font-size: inherit; height: auto !important; color: ${colors.black} !important;
      background-color: ${colors.white} !important; border-radius: 0 !important; -moz-appearance: textfield;
    }
    .${component.wrapper} input + button {
      height: 100% !important; border: 0 !important; padding: 0 !important; margin: 0 !important;
      cursor: pointer; border-right: 2px solid ${colors.grayDark} !important;
      background-color: ${colors.grayMid} !important; display: flex !important; align-items: center !important;
    }
    .${component.wrapper} input::-webkit-outer-spin-button, .${component.wrapper} input::-webkit-inner-spin-button {
      -webkit-appearance: none;
    }
    .${component.wrapper} input::placeholder {
      font-weight: bold !important; color: inherit !important;
    }
  `;
  container.appendChild(style);
  pixelInput.focus();
  console.log(`${bookmarklet.name}: ${bookmarklet.version}`);
} catch (error) {
  console.log(`${bookmarklet.name} : ${bookmarklet.version} : ${error}`);
}