Skip to content

Commit

Permalink
Lazy load squircle implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-harding committed Oct 6, 2024
1 parent b1cff73 commit 44ba184
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 150 deletions.
164 changes: 15 additions & 149 deletions demo/assets/js/demo.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,21 @@
import {
register,
paint,
} from "https://unpkg.com/[email protected]/index.mjs";

const IS_PAINT_SUPPORTED = CSS.supports("background", "paint(id)");

function main() {
register("https://unpkg.com/[email protected]/worklet.min.js");
customElements.define("th-squircle", Squircle);
}

class Squircle extends HTMLElement {
/** @type {number} */
_radius = 0;
/** @type {string} */
_fill = "transparent";
/** @type {number} */
_borderWidth = 0;
/** @type {string} */
_borderColor = "transparent";

static observedAttributes = [
"radius",
"fill",
"border-width",
"border-color",
];

constructor() {
super();
}

connectedCallback() {
this.classList.add("th-squircle");

if (IS_PAINT_SUPPORTED) {
this.classList.add("squircle");
} else {
const canvas = document.createElement("canvas");
this.appendChild(canvas);

const ctx = canvas.getContext("2d", {
colorSpace: "display-p3",
});

if (ctx === null) {
console.error("Could not get canvas 2D context");
return;
}

this._context = ctx;
this._width = 0;
this._height = 0;

const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const sizes = entry.borderBoxSize ?? entry.contentBoxSize;
if (sizes) {
const [{ inlineSize, blockSize }] = sizes;
this._width = inlineSize;
this._height = blockSize;
} else {
const rect = entry.contentRect;
this._width = rect.width;
this._height = rect.height;
}
this._width = Math.round(this._width);
this._height = Math.round(this._height);
canvas.width = Math.round(this._width);
canvas.height = Math.round(this._height);
ctx.scale(devicePixelRatio, devicePixelRatio);
draw(ctx, this._width, this._height);
}
});
observer.observe(this);
}
}

/**
* @param {string} name
* @param {string} _
* @param {string} newValue
*/
attributeChangedCallback(name, _, newValue) {
switch (name) {
case "radius": {
if (IS_PAINT_SUPPORTED) {
this.style.setProperty("--squircle-radius", newValue);
} else {
this._radius = Number.parseFloat(newValue);
}
break;
}

case "fill": {
if (IS_PAINT_SUPPORTED) {
this.style.setProperty("--squircle-fill", newValue);
} else {
this._fill = newValue;
}
break;
}

case "border-width": {
if (IS_PAINT_SUPPORTED) {
this.style.setProperty("--squircle-border-width", newValue);
} else {
this._borderWidth = Number.parseFloat(newValue);
}
break;
}

case "border-color": {
if (IS_PAINT_SUPPORTED) {
this.style.setProperty("--squircle-border-color", newValue);
} else {
this._borderColor = Number.parseFloat(newValue);
}
break;
}

default: {
console.warn(`Unknown attribute ${name}`);
return;
}
}

if (!IS_PAINT_SUPPORTED) {
this._scheduleRedraw();
}
}

_scheduleRedraw() {
if (this._animationFrame) return;
this._animationFrame = requestAnimationFrame(() => {
this._animationFrame = null;
draw(this._context, this._width, this._height);
const isPaintSupported = CSS.supports("background", "paint(id)");
if (isPaintSupported) {
import("https://unpkg.com/[email protected]/index.mjs").then(
({ register }) => {
register(
"https://unpkg.com/[email protected]/worklet.min.js",
);
},
);
import("./squircle-houdini.js").then(({ default: SquircleHoudini }) => {
customElements.define("th-squircle", SquircleHoudini);
});
} else {
import("./squircle-canvas.js").then(({ default: SquircleCanvas }) => {
customElements.define("th-squircle", SquircleCanvas);
});
}
}

/**
* @param {CanvasRenderingContext2D} ctx
* @param {number} dt Delta time in milliseconds
* @param {number} width
* @param {number} height
*/
function draw(ctx, width, height) {
ctx.reset();
ctx.fillStyle = "black";
paint(ctx, 0, 0, width, height, 10);
}

main();
124 changes: 124 additions & 0 deletions demo/assets/js/squircle-canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { paint } from "https://unpkg.com/[email protected]/index.mjs";

export default class SquircleCanvas extends HTMLElement {
/** @type {number} */
_radius = 0;
/** @type {string} */
_fill = "transparent";
/** @type {number} */
_borderWidth = 0;
/** @type {string} */
_borderColor = "transparent";
/** @type {number} */
_animationFrame = -1;
/** @type {CanvasRenderingContext2D} */
_context;

static observedAttributes = [
"radius",
"fill",
"border-width",
"border-color",
];

constructor() {
super();
}

connectedCallback() {
this.classList.add("th-squircle");

const canvas = document.createElement("canvas");
this.appendChild(canvas);

const ctx = canvas.getContext("2d", {
colorSpace: "display-p3",
});

if (ctx === null) {
console.error("Could not get canvas 2D context");
return;
}

this._context = ctx;
this._width = 0;
this._height = 0;

const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const sizes = entry.borderBoxSize ?? entry.contentBoxSize;
if (sizes) {
const [{ inlineSize, blockSize }] = sizes;
this._width = inlineSize;
this._height = blockSize;
} else {
const rect = entry.contentRect;
this._width = rect.width;
this._height = rect.height;
}
this._width = Math.round(this._width);
this._height = Math.round(this._height);
canvas.width = Math.round(this._width);
canvas.height = Math.round(this._height);
ctx.scale(devicePixelRatio, devicePixelRatio);
draw(ctx, this._width, this._height);
}
});
observer.observe(this);
}

/**
* @param {string} name
* @param {string} _
* @param {string} newValue
*/
attributeChangedCallback(name, _, newValue) {
switch (name) {
case "radius": {
this._radius = Number.parseFloat(newValue);
break;
}

case "fill": {
this._fill = newValue;
break;
}

case "border-width": {
this._borderWidth = Number.parseFloat(newValue);
break;
}

case "border-color": {
this._borderColor = Number.parseFloat(newValue);
break;
}

default: {
return;
}
}

this._scheduleRedraw();
}

_scheduleRedraw() {
if (this._animationFrame < 0) return;
this._animationFrame = requestAnimationFrame(() => {
this._animationFrame = -1;
draw(this._context, this._width, this._height);
});
}
}

/**
* @param {CanvasRenderingContext2D} ctx
* @param {number} dt Delta time in milliseconds
* @param {number} width
* @param {number} height
*/
function draw(ctx, width, height) {
ctx.reset();
ctx.fillStyle = "black";
paint(ctx, 0, 0, width, height, 10);
}
26 changes: 26 additions & 0 deletions demo/assets/js/squircle-houdini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default class SquircleHoudini extends HTMLElement {
static observedAttributes = [
"radius",
"fill",
"border-width",
"border-color",
];

constructor() {
super();
}

connectedCallback() {
this.classList.add("th-squircle");
this.classList.add("squircle");
}

/**
* @param {string} name
* @param {string} _
* @param {string} newValue
*/
attributeChangedCallback(name, _, newValue) {
this.style.setProperty(`--squircle-${name}`, newValue);
}
}
14 changes: 13 additions & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,25 @@
type="module"
async
></script>
<link
href="{{ '/assets/js/squircle-canvas.js' | relative_url }}"
rel="modulepreload"
/>
<link
href="{{ '/assets/js/squircle-houdini.js' | relative_url }}"
rel="modulepreload"
/>
<link
href="https://unpkg.com/[email protected]/worklet.min.js"
rel="modulepreload"
/>
<link
href="https://unpkg.com/[email protected]/index.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="{{ '/assets/css/style.css' | relative_url }}"
rel="stylesheet"
/>
<title>Squircle Demo</title>
</head>
Expand Down

0 comments on commit 44ba184

Please sign in to comment.