UNPKG

css-houdini-squircle

Version:

A tiny CSS Houdini module that allows to add a squircle shape to HTML elements

213 lines (192 loc) 6.1 kB
///////////////////////// ///////// UTILS ///////// ///////////////////////// const drawSquircle = (ctx, geom, radii, smooth, lineWidth, color) => { const defaultFill = color; const lineWidthOffset = lineWidth / 2; // OPEN LEFT-TOP CORNER ctx.beginPath(); ctx.lineTo(radii[0], lineWidthOffset); // TOP-RIGHT CORNER ctx.lineTo(geom.width - radii[1], lineWidthOffset); ctx.bezierCurveTo( geom.width - radii[1] / smooth, lineWidthOffset, // first bezier point geom.width - lineWidthOffset, radii[1] / smooth, // second bezier point geom.width - lineWidthOffset, radii[1] // last connect point ); // BOTTOM-RIGHT CORNER ctx.lineTo(geom.width - lineWidthOffset, geom.height - radii[2]); ctx.bezierCurveTo( geom.width - lineWidthOffset, geom.height - radii[2] / smooth, // first bezier point geom.width - radii[2] / smooth, geom.height - lineWidthOffset, // second bezier point geom.width - radii[2], geom.height - lineWidthOffset // last connect point ); // BOTTOM-LEFT CORNER ctx.lineTo(radii[3], geom.height - lineWidthOffset); ctx.bezierCurveTo( radii[3] / smooth, geom.height - lineWidthOffset, // first bezier point lineWidthOffset, geom.height - radii[3] / smooth, // second bezier point lineWidthOffset, geom.height - radii[3] // last connect point ); // CLOSE LEFT-TOP CORNER ctx.lineTo(lineWidthOffset, radii[0]); ctx.bezierCurveTo( lineWidthOffset, radii[0] / smooth, // first bezier point radii[0] / smooth, lineWidthOffset, // second bezier point radii[0], lineWidthOffset // last connect point ); ctx.closePath(); if (lineWidth) { ctx.strokeStyle = defaultFill; ctx.lineWidth = lineWidth; ctx.stroke(); } else { ctx.fillStyle = defaultFill; ctx.fill(); } }; ///////////////////////// ///////// CLASS ///////// ///////////////////////// class SquircleClass { static get contextOptions() { return { alpha: true }; } static get inputProperties() { return [ "--squircle-radius", "--squircle-radius-top-left", // <-- if the order changes ... "--squircle-radius-top-right", "--squircle-radius-bottom-right", "--squircle-radius-bottom-left", // --> the slice values of individualRadiiProps need to be updated "--squircle-smooth", "--squircle-outline", "--squircle-fill" ]; } paint(ctx, geom, properties) { const smoothRatio = 10; const distanceRatio = 1.8; const squircleSmooth = parseFloat( properties.get("--squircle-smooth") * smoothRatio ); // CALCULATE RADII const individualRadiiProps = SquircleClass.inputProperties.slice(1, 5); let squircleRadii = individualRadiiProps.map((prop) => { const value = properties.get(prop); return value ? parseInt(value, 10) * distanceRatio : NaN; }); let shorthand_R; // Check if any of the individual radii are NaN, if so, process the shorthand if (squircleRadii.some(isNaN)) { const radiusRegex = /([0-9]+[a-z%]*)/g; // Units are ignored. const radius_shorthand = properties.get("--squircle-radius").toString(); const matches = radius_shorthand.match(radiusRegex); if (matches) { shorthand_R = matches.map((val) => parseInt(val, 10) * distanceRatio); while (shorthand_R.length < 4) { if (shorthand_R.length === 1) { // If there's only one value, duplicate it for all corners shorthand_R.push(shorthand_R[0]); } else if (shorthand_R.length === 2) { // If there are two values, first one applies to top and bottom, second to left and right shorthand_R = [ shorthand_R[0], shorthand_R[1], shorthand_R[0], shorthand_R[1] ]; } else if (shorthand_R.length === 3) { // If there are three values, first applies to top, second to right and left, third to bottom shorthand_R = [ shorthand_R[0], shorthand_R[1], shorthand_R[2], shorthand_R[1] ]; } } } else { // if no radii at all are provided, set default radius = 8, otherwise set undefined ones to 0 const defaultRadius = squircleRadii.every(isNaN) ? 8 * distanceRatio : 0; shorthand_R = [ defaultRadius, defaultRadius, defaultRadius, defaultRadius ]; } } // Replace NaN values in radii with corresponding values from shorthand or default squircleRadii = squircleRadii.map((val, i) => isNaN(val) ? shorthand_R[i] : val ); const squrcleOutline = parseFloat(properties.get("--squircle-outline"), 10); const squrcleColor = properties.get("--squircle-fill").toString(); const isSmooth = () => { if (typeof properties.get("--squircle-smooth")[0] !== "undefined") { if (squircleSmooth === 0) { return 1; } return squircleSmooth; } else { return 10; } }; const isOutline = () => { if (squrcleOutline) { return squrcleOutline; } else { return 0; } }; const isColor = () => { if (squrcleColor) { return squrcleColor; } else { return "#f45"; } }; const maxRadius = Math.max(...squircleRadii); if (maxRadius < geom.width / 2 && maxRadius < geom.height / 2) { drawSquircle( ctx, geom, squircleRadii, isSmooth(), isOutline(), isColor() ); } else { const minRadius = Math.min(geom.width / 2, geom.height / 2); drawSquircle( ctx, geom, squircleRadii.map(() => minRadius), isSmooth(), isOutline(), isColor() ); } } } ///////////////////////// /////// REGISTER //////// ///////////////////////// if (typeof registerPaint !== "undefined") { registerPaint("squircle", SquircleClass); }