Search code examples
csscanvasnext.jscss-selectorsborder

Draw paper "cut" borders with html canvas or pseudo-elements


I'm trying to get a very specific "art nouveau" result on some paper surface because designers like to make us suffer. I'm working on a NextJS project with MUI as my main design solution, but have a lot of CSS going on in the back.

Here is what I'm trying to do (don't mind imperfections at the bottom of the frame):

Visual result of a paper with multiple complex inner borders

The main objective to achieve are those inner borders with "cut corners", but I can't just use a plain image or .svg because they need to have specific colors and sizes.

The ideal would be that they fit their parent width and height in order to be fully responsive, because the Paper will eventually be turned into a component to contain text, images, etc.

How do I make such borders?

My actual code structure is quite simple:

<div className="mainWrapper">
   <div className="border1">
      // any text or content
   </div>
</div>
  • Initially, I tried to make something with the ::after pseudo-element and the clip-path property, but unfortunately it does not support borders, and calculations in order to make it responsive were way too complicated...

  • I tried using the <canvas> with useRef (because my project uses SSR, so I had to wrap the canvas logic inside of a useEffect) but results were far from what I'm trying to do...


Solution

  • I think I've found a way but it's really tiring... (and complex because CSS):

        const styles = {
            mainSurface: {
                width: "100%",
                backgroundColor: "blue"
            },
            bordersContainer: {
                backgroundColor: "blue!important",
                content: "''",
                position: "relative"
            },
            borderTL: {
                content: "''",
                position: "absolute",
                height: 32,
                width: 32,
                top: 16,
                left: 16,
                backgroundColor: "transparent",
                borderRight: "4px solid",
                borderColor: "red",
                transform: "rotate(45deg)"
            },
            borderBL: {
                content: "''",
                position: "absolute",
                height: 32,
                width: 32,
                bottom: 16,
                left: 16,
                backgroundColor: "transparent",
                borderTop: "4px solid",
                borderColor: "red",
                transform: "rotate(45deg)"
            },
            borderTR: {
                content: "''",
                position: "absolute",
                height: 32,
                width: 32,
                top: 16,
                right: 16,
                backgroundColor: "transparent",
                borderBottom: "4px solid",
                borderColor: "red",
                transform: "rotate(45deg)"
            },
            borderBR: {
                content: "''",
                position: "absolute",
                height: 32,
                width: 32,
                bottom: 16,
                right: 16,
                backgroundColor: "transparent",
                borderLeft: "4px solid",
                borderColor: "red",
                transform: "rotate(45deg)"
            },
    
            borderLeft: {
                position: "absolute",
                content: "''",
                height: "calc(100% - 104px)",
                width: 32,
                top: 52,
                backgroundColor: "transparent",
                borderRight: "4px solid",
                borderColor: "red",
            },
            borderRight: {
                position: "absolute",
                content: "''",
                height: "calc(100% - 104px)",
                width: 32,
                top: 52,
                right: 0,
                backgroundColor: "transparent",
                borderLeft: "4px solid",
                borderColor: "red",
            },
            borderTop: {
                position: "absolute",
                content: "''",
                height: 32,
                width: "calc(100% - 104px)",
                top: 0,
                left: 52,
                backgroundColor: "transparent",
                borderBottom: "4px solid",
                borderColor: "red",
            },
            borderBottom: {
                position: "absolute",
                content: "''",
                height: 32,
                width: "calc(100% - 104px)",
                bottom: 0,
                left: 52,
                backgroundColor: "transparent",
                borderTop: "4px solid",
                borderColor: "red",
            },
            contentBox: {
                height: "100%",
                width: "100%",
                py: 6,
                px: 8,
                position: "relative"
            }
        }
    
        return (
            <>
               <Box sx={styles.mainSurface}>
                   <Box sx={styles.bordersContainer}>
                       <Box sx={styles.borderTL}></Box>
                       <Box sx={styles.borderTR}></Box>
                       <Box sx={styles.borderBL}></Box>
                       <Box sx={styles.borderBR}></Box>
                       <Box sx={styles.borderLeft}></Box>
                       <Box sx={styles.borderRight}></Box>
                       <Box sx={styles.borderTop}></Box>
                       <Box sx={styles.borderBottom}></Box>
                       <Box sx={styles.contentBox}>
                           {props.children}
                       </Box>
                   </Box>
               </Box>
            </>
        )
    
    

    Giving something like this, clean and responsive:

    enter image description here

    Now, adding another "classic" bordered frame will be easier. Thanks to anyone who tried to help! I hope this'll be useful to someone else in the future!

    I'll try to optimize it and upload it somewhere eventually...