mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-27 02:05:34 +02:00
canonical
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { hatch } from "./util/hatchery.js";
|
||||
|
||||
/**
|
||||
* The base API class, responsible for such things as setting up canvas event
|
||||
* handling, method accumulation, custom element binding, etc. etc.
|
||||
@@ -6,7 +8,12 @@
|
||||
*/
|
||||
class BaseAPI {
|
||||
static get privateMethods() {
|
||||
return [`constructor`, `addListeners`, `getCursorCoords`]
|
||||
return [
|
||||
`constructor`,
|
||||
`createHatchPatterns`,
|
||||
`addListeners`,
|
||||
`getCursorCoords`,
|
||||
]
|
||||
.concat(this.superCallers)
|
||||
.concat(this.eventHandlers);
|
||||
}
|
||||
@@ -44,6 +51,7 @@ class BaseAPI {
|
||||
} else {
|
||||
this.canvas = document.createElement(`canvas`);
|
||||
}
|
||||
this.HATCHING = hatch(canvasBuildFunction);
|
||||
this.addListeners();
|
||||
this.setSize(width, height);
|
||||
this.setup();
|
||||
@@ -201,6 +209,11 @@ class BaseAPI {
|
||||
setup() {
|
||||
// console.log(`setup`);
|
||||
this.movable = [];
|
||||
this.font = {
|
||||
size: 10,
|
||||
weight: 400,
|
||||
family: `arial`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,6 +246,9 @@ function enhanceContext(ctx) {
|
||||
lineWidth: ctx.lineWidth,
|
||||
textAlign: ctx.textAlign,
|
||||
transform: [m.a, m.b, m.c, m.d, m.e, m.f],
|
||||
font: ctx.font,
|
||||
shadowColor: ctx.shadowColor,
|
||||
shadowBlur: ctx.shadowColor,
|
||||
};
|
||||
styles.push(e);
|
||||
};
|
||||
|
@@ -26,6 +26,12 @@ class GraphicsAPI extends BaseAPI {
|
||||
`CENTER`,
|
||||
`LEFT`,
|
||||
`RIGHT`,
|
||||
`HATCH1`,
|
||||
`HATCH2`,
|
||||
`HATCH3`,
|
||||
`HATCH4`,
|
||||
`HATCH5`,
|
||||
`HATCH6`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -64,6 +70,25 @@ class GraphicsAPI extends BaseAPI {
|
||||
get RIGHT() {
|
||||
return `right`;
|
||||
}
|
||||
// hatching patterns
|
||||
get HATCH1() {
|
||||
return this.HATCHING[0];
|
||||
}
|
||||
get HATCH2() {
|
||||
return this.HATCHING[1];
|
||||
}
|
||||
get HATCH3() {
|
||||
return this.HATCHING[2];
|
||||
}
|
||||
get HATCH4() {
|
||||
return this.HATCHING[3];
|
||||
}
|
||||
get HATCH5() {
|
||||
return this.HATCHING[4];
|
||||
}
|
||||
get HATCH6() {
|
||||
return this.HATCHING[5];
|
||||
}
|
||||
|
||||
onMouseDown(evt) {
|
||||
super.onMouseDown(evt);
|
||||
@@ -113,6 +138,20 @@ class GraphicsAPI extends BaseAPI {
|
||||
points.forEach((p) => this.movable.push(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the canvas to an image
|
||||
*/
|
||||
toDataURL() {
|
||||
return this.canvas.toDataURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw an image onto the canvas
|
||||
*/
|
||||
image(img, x = 0, y = 0, w, h) {
|
||||
this.ctx.drawImage(img, x, y, w || img.width, h || img.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* transforms: translate
|
||||
*/
|
||||
@@ -251,6 +290,22 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.setStroke(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a text stroke/color
|
||||
*/
|
||||
setTextStroke(color, weight) {
|
||||
this.textStroke = color;
|
||||
this.strokeWeight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use text stroking.
|
||||
*/
|
||||
noTextStroke() {
|
||||
this.textStroke = false;
|
||||
this.strokeWeight = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context lineWidth
|
||||
*/
|
||||
@@ -258,6 +313,52 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.ctx.lineWidth = `${width}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font size
|
||||
*/
|
||||
setFontSize(px) {
|
||||
this.font.size = px;
|
||||
this.setFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font weight (CSS name/number)
|
||||
*/
|
||||
setFontWeight(val) {
|
||||
this.font.weight = val;
|
||||
this.setFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font family by name
|
||||
*/
|
||||
setFontFamily(name) {
|
||||
this.font.family = name;
|
||||
this.setFont();
|
||||
}
|
||||
|
||||
setFont(font) {
|
||||
font =
|
||||
font || `${this.font.weight} ${this.font.size}px ${this.font.family}`;
|
||||
this.ctx.font = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text shadow
|
||||
*/
|
||||
setShadow(color, px) {
|
||||
this.ctx.shadowColor = color;
|
||||
this.ctx.shadowBlur = px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable text shadow
|
||||
*/
|
||||
noShadow() {
|
||||
this.ctx.shadowColor = `transparent`;
|
||||
this.ctx.shadowBlur = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache all styling values
|
||||
*/
|
||||
@@ -319,12 +420,17 @@ class GraphicsAPI extends BaseAPI {
|
||||
x = x.x;
|
||||
}
|
||||
const ctx = this.ctx;
|
||||
ctx.cacheStyle();
|
||||
if (alignment) {
|
||||
ctx.cacheStyle();
|
||||
ctx.textAlign = alignment;
|
||||
}
|
||||
if (this.textStroke) {
|
||||
this.ctx.lineWidth = this.strokeWeight;
|
||||
this.setStroke(this.textStroke);
|
||||
this.ctx.strokeText(str, x, y);
|
||||
}
|
||||
this.ctx.fillText(str, x, y);
|
||||
if (alignment) ctx.restoreStyle();
|
||||
ctx.restoreStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,18 +445,14 @@ class GraphicsAPI extends BaseAPI {
|
||||
* Draw a function plot from [start] to [end] in [steps] steps.
|
||||
* Returns the plot shape so that it can be cached for redrawing.
|
||||
*/
|
||||
plot(fn, start = 0, end = 1, steps = 24) {
|
||||
const ctx = this.ctx;
|
||||
ctx.cacheStyle();
|
||||
ctx.fillStyle = `transparent`;
|
||||
plot(fn, start = 0, end = 1, steps = 24, xscale = 1, yscale = 1) {
|
||||
const interval = end - start;
|
||||
this.start();
|
||||
for (let i = 0, e = steps - 1, v; i < steps; i++) {
|
||||
v = fn(start + (interval * i) / e);
|
||||
this.vertex(v.x, v.y);
|
||||
this.vertex(v.x * xscale, v.y * yscale);
|
||||
}
|
||||
this.end();
|
||||
ctx.restoreStyle();
|
||||
return this.currentShape;
|
||||
}
|
||||
|
||||
@@ -378,9 +480,17 @@ class GraphicsAPI extends BaseAPI {
|
||||
/**
|
||||
* Draw a previously created shape
|
||||
*/
|
||||
drawShape(shape) {
|
||||
this.currentShape = shape;
|
||||
drawShape(...shapes) {
|
||||
shapes = shapes.map((s) => {
|
||||
if (s instanceof Shape) return s;
|
||||
return new Shape(this.POLYGON, undefined, s);
|
||||
});
|
||||
this.currentShape = shapes[0].copy();
|
||||
for (let i = 1; i < shapes.length; i++) {
|
||||
this.currentShape.merge(shapes[i]);
|
||||
}
|
||||
this.end();
|
||||
this.STARTREPORTING = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,6 +626,10 @@ class GraphicsAPI extends BaseAPI {
|
||||
return Math.max(...v);
|
||||
}
|
||||
|
||||
approx(v1, v2, epsilon = 0.001) {
|
||||
return Math.abs(v1 - v2) < epsilon;
|
||||
}
|
||||
|
||||
sin(v) {
|
||||
return Math.sin(v);
|
||||
}
|
||||
@@ -554,4 +668,4 @@ class GraphicsAPI extends BaseAPI {
|
||||
}
|
||||
}
|
||||
|
||||
export { GraphicsAPI, Bezier, Vector, Matrix };
|
||||
export { GraphicsAPI, Bezier, Vector, Matrix, Shape };
|
||||
|
111
docs/js/custom-element/api/util/hatchery.js
Normal file
111
docs/js/custom-element/api/util/hatchery.js
Normal file
@@ -0,0 +1,111 @@
|
||||
const HATCHING = [];
|
||||
|
||||
/**
|
||||
* Build hatching patterns. These are built fully unrolled,
|
||||
* mostly because they're small and there's no actual benefit
|
||||
* to abstracting the drawing for only six patterns.
|
||||
*/
|
||||
function hatch(canvasBuildFunction) {
|
||||
if (HATCHING.length > 0) {
|
||||
return HATCHING;
|
||||
}
|
||||
|
||||
let cvs,
|
||||
ctx,
|
||||
w = 9,
|
||||
h = 9;
|
||||
|
||||
if (canvasBuildFunction) {
|
||||
let b = canvasBuildFunction(w, h);
|
||||
cvs = b.canvas;
|
||||
ctx = b.ctx;
|
||||
} else {
|
||||
cvs = document.createElement("canvas");
|
||||
cvs.width = w;
|
||||
cvs.height = h;
|
||||
ctx = cvs.getContext(`2d`);
|
||||
}
|
||||
|
||||
ctx.fillStyle = `#0000FF30`;
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
const paint = (x, y) => ctx.fillRect(x, y, 1, 1);
|
||||
|
||||
// pattern: \
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
paint(0, 0);
|
||||
paint(1, 1);
|
||||
paint(2, 2);
|
||||
paint(3, 3);
|
||||
paint(4, 4);
|
||||
paint(5, 5);
|
||||
paint(6, 6);
|
||||
paint(7, 7);
|
||||
paint(8, 8);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
// pattern: /
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
paint(0, 8);
|
||||
paint(1, 7);
|
||||
paint(2, 6);
|
||||
paint(3, 5);
|
||||
paint(4, 4);
|
||||
paint(5, 3);
|
||||
paint(6, 2);
|
||||
paint(7, 1);
|
||||
paint(8, 0);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
// pattern: x (without clearing, because we can overlay)
|
||||
paint(0, 0);
|
||||
paint(1, 1);
|
||||
paint(2, 2);
|
||||
paint(3, 3);
|
||||
paint(5, 5);
|
||||
paint(6, 6);
|
||||
paint(7, 7);
|
||||
paint(8, 8);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
// pattern: |
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
paint(4, 0);
|
||||
paint(4, 1);
|
||||
paint(4, 2);
|
||||
paint(4, 3);
|
||||
paint(4, 4);
|
||||
paint(4, 5);
|
||||
paint(4, 6);
|
||||
paint(4, 7);
|
||||
paint(4, 8);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
// pattern: -
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
paint(0, 4);
|
||||
paint(1, 4);
|
||||
paint(2, 4);
|
||||
paint(3, 4);
|
||||
paint(4, 4);
|
||||
paint(5, 4);
|
||||
paint(6, 4);
|
||||
paint(7, 4);
|
||||
paint(8, 4);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
// pattern: + (without clearing, because we can overlap)
|
||||
paint(4, 0);
|
||||
paint(4, 1);
|
||||
paint(4, 2);
|
||||
paint(4, 3);
|
||||
paint(4, 5);
|
||||
paint(4, 6);
|
||||
paint(4, 7);
|
||||
paint(4, 8);
|
||||
HATCHING.push(ctx.createPattern(cvs, "repeat"));
|
||||
|
||||
return HATCHING;
|
||||
}
|
||||
|
||||
export { hatch };
|
@@ -4,10 +4,23 @@
|
||||
* cubic Bezier curves.
|
||||
*/
|
||||
class Shape {
|
||||
constructor(type, factor) {
|
||||
constructor(type, factor, points = []) {
|
||||
this.first = false;
|
||||
this.segments = [];
|
||||
this.addSegment(type, factor);
|
||||
points.forEach((p) => this.vertex(p));
|
||||
}
|
||||
merge(other) {
|
||||
if (!other.segments) {
|
||||
other = { segments: [new Segment(Shape.POLYGON, undefined, other)] };
|
||||
}
|
||||
other.segments.forEach((s) => this.segments.push(s));
|
||||
}
|
||||
copy() {
|
||||
const copy = new Shape(this.type, this.factor);
|
||||
copy.first = this.first;
|
||||
copy.segments = this.segments.map((s) => s.copy());
|
||||
return copy;
|
||||
}
|
||||
addSegment(type, factor) {
|
||||
this.currentSegment = new Segment(type, factor);
|
||||
@@ -30,10 +43,15 @@ Shape.BEZIER = `Bezier`;
|
||||
* A shape subpath
|
||||
*/
|
||||
class Segment {
|
||||
constructor(type, factor) {
|
||||
constructor(type, factor, points = []) {
|
||||
this.type = type;
|
||||
this.factor = factor;
|
||||
this.points = [];
|
||||
this.points = points;
|
||||
}
|
||||
copy() {
|
||||
const copy = new Segment(this.type, this.factor);
|
||||
copy.points = JSON.parse(JSON.stringify(this.points));
|
||||
return copy;
|
||||
}
|
||||
add(p) {
|
||||
this.points.push(p);
|
||||
|
@@ -40,8 +40,8 @@ class GraphicsElement extends CustomElement {
|
||||
return `
|
||||
:host([hidden]) { display: none; }
|
||||
:host style { display: none; }
|
||||
:host canvas { position: relative; z-index: 1; display: block; margin: auto; border-radius: 0; box-sizing: content-box!important; padding: 1px; }
|
||||
:host canvas:focus { border: 1px solid red; padding: 0px; }
|
||||
:host canvas { position: relative; z-index: 1; display: block; margin: auto; border-radius: 0; box-sizing: content-box!important; border: 1px solid lightgrey; }
|
||||
:host canvas:focus { border: 1px solid red; }
|
||||
:host a.view-source { display: block; position:relative; top: -0.6em; margin-bottom: -0.2em; font-size: 60%; text-decoration: none; }
|
||||
:host label { display: block; font-style:italic; font-size: 0.9em; text-align: right; }
|
||||
`;
|
||||
@@ -171,7 +171,7 @@ class GraphicsElement extends CustomElement {
|
||||
const height = this.getAttribute(`height`, 200);
|
||||
|
||||
this.code = `
|
||||
import { GraphicsAPI, Bezier, Vector, Matrix } from "${MODULE_PATH}/api/graphics-api.js";
|
||||
import { GraphicsAPI, Bezier, Vector, Matrix, Shape } from "${MODULE_PATH}/api/graphics-api.js";
|
||||
|
||||
${globalCode}
|
||||
|
||||
|
76
docs/js/site/faster-lazy-loading.js
Normal file
76
docs/js/site/faster-lazy-loading.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* This file takes any <img> with loading="lazy" and rewrites it
|
||||
* so that it starts to load much earlier than browsers would load
|
||||
* them, so that they're already done by the time the user gets to
|
||||
* them. This prevents the ridiculous "don't start loading the img
|
||||
* until we're already looking at the empty bounding box".
|
||||
*/
|
||||
|
||||
const images = Array.from(
|
||||
document.querySelectorAll(`img[loading=lazy]`)
|
||||
).filter((img) => img.parentNode.nodeName !== `FALLBACK-IMAGE`);
|
||||
|
||||
// First, make images inert. As this happens before the document
|
||||
// becomes active, this prevents images from loading anything.
|
||||
//
|
||||
// Also, by having JS deactivate the images, anyone running with
|
||||
// JS disabled will still see images load in properly. In fact,
|
||||
// the "lazy" attribute gets ignored when JS is disabled and all
|
||||
// images will just load in immediately.
|
||||
|
||||
images.forEach((img) => {
|
||||
// non-negotiable order of operations:
|
||||
img.dataset.src = img.src;
|
||||
img.src = ``;
|
||||
img.removeAttribute(`loading`);
|
||||
});
|
||||
|
||||
// Then tack on the functionality that reactivates them based on viewport distance.
|
||||
|
||||
let lock = false;
|
||||
let retry = false;
|
||||
|
||||
/**
|
||||
* Test all images during scroll, in a way that doesn't hang the browser.
|
||||
*/
|
||||
function testImages() {
|
||||
if (lock) {
|
||||
if (retry) retry = clearTimeout(retry);
|
||||
retry = setTimeout(testImages, 200);
|
||||
}
|
||||
|
||||
lock = true;
|
||||
|
||||
let top = window.scrollY;
|
||||
let height = document.documentElement.clientHeight;
|
||||
let bottom = top + height;
|
||||
|
||||
for (let pos = images.length - 1; pos >= 0; pos--) {
|
||||
test(images[pos], pos, top, bottom, height);
|
||||
}
|
||||
|
||||
if (images.length === 0) {
|
||||
window.removeEventListener("scroll", testImages);
|
||||
if (retry) clearTimeout(retry);
|
||||
}
|
||||
|
||||
lock = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test individual images for whether or not they should load.
|
||||
*/
|
||||
function test(img, pos, top, bottom, threshold) {
|
||||
top = Math.abs(img.offsetTop - top) < threshold;
|
||||
bottom = Math.abs(img.offsetTop + img.offsetHeight - bottom) < threshold;
|
||||
|
||||
if (top || bottom) {
|
||||
img.src = img.dataset.src;
|
||||
images.splice(pos, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember to listen for scroll passively. If you don't, bad things happen.
|
||||
*/
|
||||
window.addEventListener("scroll", testImages, { passive: true });
|
Reference in New Issue
Block a user