5
\$\begingroup\$

I wrote a project that allows you to make CSS corner rounding more consistent and, in my opinion, nicer. Here's full GIT repo with an example and a README.md: https://github.com/YurichBRO/smart-rounding.

In short - in CSS corner rounding is done through changing shape of an ellipse for each corner, that dictates the shape of this corner. With this code you can make parent and child elements share the same center point for their ellipses.

I would like you to find any flaws and bugs in my code. I'm not sure whether this code is optimised and reliable enough to be used in production. Here it is:

/** * Automatically use CSS rounding from parent or from child. * * terminology: * rounding value - a list of 2 numbers that represent rounding of a single corner; * CSS value - a string that represents rounding of a single corner using CSS; * offset - list of 4 numbers that represent top, right, bottom, left offsets; */ function parseCssValue(value) { if (!value.includes(" ")) { const singleValue = parseFloat(value); return isNaN(singleValue) ? [0, 0] : [singleValue, singleValue]; } const parts = value.split(" "); const firstValue = parseFloat(parts[0]); const secondValue = parseFloat(parts[1]); return [ isNaN(firstValue) ? 0 : firstValue, isNaN(secondValue) ? firstValue : secondValue ]; } function roundingValueToCssValue(value) { return `${value[0]}px ${value[1]}px`; } function addNewCorner(corner, offsetX, offsetY) { return [corner[0] + offsetX, corner[1] + offsetY]; } function addOffsetToRounding(rounding, offset) { return [ addNewCorner(rounding[0], offset[3], offset[0]), addNewCorner(rounding[1], offset[1], offset[0]), addNewCorner(rounding[2], offset[3], offset[2]), addNewCorner(rounding[3], offset[1], offset[2]), ]; } function subNewCorner(corner, offsetX, offsetY) { return [corner[0] - offsetX, corner[1] - offsetY]; } function subOffsetFromRounding(rounding, offset) { return [ subNewCorner(rounding[0], offset[3], offset[0]), subNewCorner(rounding[1], offset[1], offset[0]), subNewCorner(rounding[2], offset[3], offset[2]), subNewCorner(rounding[3], offset[1], offset[2]), ]; } function fromCssProperties(elem, properties, valueFunction) { const styles = getComputedStyle(elem); const values = []; for (let i = 0; i < properties.length; i++) { const cssValue = styles[properties[i]]; values[i] = valueFunction(cssValue); } return values; } const keyToCss = { rounding: [ "borderTopLeftRadius", "borderTopRightRadius", "borderBottomLeftRadius", "borderBottomRightRadius", ], margin: [ "marginTop", "marginRight", "marginBottom", "marginLeft", ], padding: [ "paddingTop", "paddingRight", "paddingBottom", "paddingLeft", ], border: [ "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth", ] } const getPaddings = (elem) => fromCssProperties(elem, keyToCss.padding, parseFloat); const getMargins = (elem) => fromCssProperties(elem, keyToCss.margin, parseFloat); const getRounding = (elem) => fromCssProperties(elem, keyToCss.rounding, parseCssValue); const getBorder = (elem) => fromCssProperties(elem, keyToCss.border, parseFloat); function getOuterRounding(elem) { const rounding = getRounding(elem); const margins = getMargins(elem); return addOffsetToRounding(rounding, margins); } function getInnerRounding(elem) { const rounding = getRounding(elem); const paddings = getPaddings(elem); const border = getBorder(elem); const withoutPaddings = subOffsetFromRounding(rounding, paddings); return subOffsetFromRounding(withoutPaddings, border); } function applyRounding(elem, rounding) { for (let i = 0; i < rounding.length; i++) { const cssKey = keyToCss.rounding[i]; const cssValue = roundingValueToCssValue(rounding[i]); elem.style[cssKey] = cssValue; } } export function useOuterRounding(target, source) { const sourceRounding = getOuterRounding(source); const targetPaddings = getPaddings(target); const targetRounding = addOffsetToRounding(sourceRounding, targetPaddings); applyRounding(target, targetRounding); } export function useInnerRounding(target, source) { const sourceRounding = getInnerRounding(source); const targetMargins = getMargins(target); const targetRounding = subOffsetFromRounding(sourceRounding, targetMargins); applyRounding(target, targetRounding); } export function useRoundingFromChild(elem, selector) { const source = elem.querySelector(selector); if (source === null) return false; useOuterRounding(elem, source); return true; } export function useRoundingFromParent(elem, selector) { const targets = elem.querySelectorAll(selector); if (targets.length === 0) return false; for (const target of targets) { useInnerRounding(target, elem); } return true; } export function useRoundingFromChildOnAll(selectorPairs) { for (const targetSelector in selectorPairs) { const targets = document.querySelectorAll(targetSelector); for (const target of targets) { const result = useRoundingFromChild(target, selectorPairs[targetSelector]); if (result) { console.log(`Applied rounding on '${targetSelector}' using '${selectorPairs[targetSelector]}'`); } else { console.warn("Could not find source element for", target); } } } } export function useRoundingFromParentOnAll(selectorPairs) { for (const sourceSelector in selectorPairs) { const sources = document.querySelectorAll(sourceSelector); for (const source of sources) { const result = useRoundingFromParent(source, selectorPairs[sourceSelector]); if (result) { console.log(`Applied rounding on '${selectorPairs[sourceSelector]}' using '${sourceSelector}'`); } else { console.warn("Could not find target elements for", source); } } } } 
\$\endgroup\$
2
  • \$\begingroup\$It took me ages until I understood that you are talking of rounding corners and not numbers. Could you explain in more detail what this code does?\$\endgroup\$
    – RoToRa
    CommentedOct 16, 2024 at 10:32
  • \$\begingroup\$@RoToRa README in the repo provides necessary information, I will dublicate some of it here.\$\endgroup\$
    – yurich
    CommentedOct 17, 2024 at 6:23

1 Answer 1

3
\$\begingroup\$

A quick review;

  • Readability, using arrays with hard-coded indexes is not super readable,

    rounding[0] //Meh rounding[TOP_LEFT] //Better rounding.topLeft //Most readable 
  • Global name space, this code pollutes it

  • Convention, <verb><thing> is better so perhaps subNewCorner -> newSubCorner

  • Leverage features, fromCssProperties could use Array.map, similarly, useRoundingFromParentOnAll could probably be a readable 1 liner.

  • Readability, I would have put keyToCss and the 4 functions under it at the top

  • Practices, I would avoid console.log, put some level of debug mode, so your module is not polluting to console

\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.