UNPKG

inverted-corners

Version:

A Houdini Paint worklet for inverted corners using the CSS Paint API.

7 lines 5.78 kB
/** * Inverted Corners (https://inverted-corners.netlify.app/) * @author over-engineer <dev@over-engineer.com> * @copyright 2021 over-engineer * @license MIT */ (()=>{const workletBlob=URL.createObjectURL(new Blob(["\nif (typeof registerPaint !== 'undefined') {\n class InvertedCornersPainter {\n static get inputProperties() {\n return [\n '--corner-radius',\n '--background',\n 'list-style-image',\n ];\n }\n\n /**\n * Return an array containing the radii of all four corners\n *\n * Take into account the shorthand syntax where the top-left\n * corner has the same radius as the bottom-right corner,\n * and the top-right corner has the same radius as the\n * bottom-left. All radii default to 0\n *\n * @param {number[]} radii\n * @return {number[]}\n */\n _getRadii(radii) {\n const [a = 0, b = 0, c = 0, d = 0] = radii;\n\n if (radii.length === 2) {\n return [a, b, a, b];\n }\n\n return [a, b, c, d];\n }\n\n /**\n * Check whether the given corner is inverted or not\n *\n * A corner is inverted when its radius is a negative value\n *\n * @param {number} radius\n * @return {boolean}\n */\n _isInverted(radius) {\n return radius < 0;\n }\n\n paint(ctx, geom, properties) {\n // Read and clean up input properties\n const radii = properties\n .get('--corner-radius')\n .toString()\n .trim()\n .split(' ')\n .map((radius) => parseInt(radius));\n \n const gradProps = properties\n .get('--background')\n .toString()\n .split(',')\n .map((prop) => prop.trim());\n\n gradProps[0] = gradProps[0].replace('deg', '');\n\n /* Using list-style-image to workaround an issue where images\n * wouldn't load with custom image properties on Chrome/Opera/Edge */\n const image = properties.get('list-style-image');\n\n // Set default values for the gradient background\n let angle = 0;\n let colors = gradProps;\n\n // Check whether an angle was specified\n if (!isNaN(+gradProps[0])) {\n [angle, ...colors] = gradProps;\n angle = +angle;\n }\n\n // Set default radii and handle the --corner-radius shorthand syntax\n const [\n topLeft,\n topRight,\n bottomRight,\n bottomLeft,\n ] = this._getRadii(radii);\n\n // Calculate the maximum radius\n const maxInvRadius = Math.abs(Math.min(...radii, 0));\n\n // Set default color\n ctx.fillStyle = '#000';\n\n // Check if we a gradient background was specified\n if (colors.length >= 1) {\n // Convert degrees to radians\n angle = angle * Math.PI / 180;\n\n // Calculate the gradient length to fit diagonal\n const gradLength = Math.sqrt(geom.width ** 2 + geom.height ** 2) / 2;\n\n const x1 = geom.width / 2 - Math.cos(angle) * gradLength;\n const y1 = geom.height / 2 - Math.sin(angle) * gradLength;\n const x2 = geom.width / 2 + Math.cos(angle) * gradLength;\n const y2 = geom.height / 2 + Math.sin(angle) * gradLength;\n\n // Calculate the color stops step\n const step = 1 / (colors.length - 1);\n\n // Create the linear gradient\n const grad = ctx.createLinearGradient(x1, y1, x2, y2);\n\n for (let i = 0; i < colors.length; i += 1) {\n // Input properties are given in <color> <colorStop> format\n const [color, colorStop] = colors[i].split(' ');\n\n // Default offset to equally distributed color stops\n let offset = i === colors.length - 1 ? 1 : i * step;\n\n if (colorStop) {\n // Override with custom color stop, if specified\n offset = colorStop;\n }\n\n grad.addColorStop(offset, color);\n }\n\n ctx.fillStyle = grad;\n }\n \n ctx.beginPath();\n\n // Draw the background\n if (this._isInverted(topLeft)) {\n ctx.moveTo(maxInvRadius - Math.abs(topLeft), 0);\n } else {\n ctx.moveTo(maxInvRadius + Math.abs(topLeft), 0);\n }\n\n ctx.quadraticCurveTo(maxInvRadius, 0, maxInvRadius, Math.abs(topLeft));\n\n ctx.lineTo(maxInvRadius, geom.height - Math.abs(bottomLeft));\n \n if (this._isInverted(bottomLeft)) {\n ctx.quadraticCurveTo(maxInvRadius, geom.height, maxInvRadius - Math.abs(bottomLeft), geom.height);\n } else {\n ctx.quadraticCurveTo(maxInvRadius, geom.height, maxInvRadius + Math.abs(bottomLeft), geom.height);\n }\n\n if (this._isInverted(bottomRight)) {\n ctx.lineTo(geom.width - maxInvRadius + Math.abs(bottomRight), geom.height);\n } else {\n ctx.lineTo(geom.width - maxInvRadius - Math.abs(bottomRight), geom.height);\n }\n\n ctx.quadraticCurveTo(geom.width - maxInvRadius, geom.height, geom.width - maxInvRadius, geom.height - Math.abs(bottomRight));\n\n ctx.lineTo(geom.width - maxInvRadius, Math.abs(topRight));\n\n if (this._isInverted(topRight)) {\n ctx.quadraticCurveTo(geom.width - maxInvRadius, 0, geom.width - maxInvRadius + Math.abs(topRight), 0);\n } else {\n ctx.quadraticCurveTo(geom.width - maxInvRadius, 0, geom.width - maxInvRadius - Math.abs(topRight), 0);\n }\n \n ctx.closePath();\n ctx.fill();\n\n // Draw the image, if one was specified\n if (image instanceof CSSImageValue) {\n ctx.clip();\n ctx.drawImage(image, 0, 0, geom.width, geom.height);\n }\n }\n }\n\n registerPaint('inverted-corners', InvertedCornersPainter);\n}"],{type:"application/javascript"}));window.CSS.paintWorklet.addModule(workletBlob)})();