@vanillawc/wc-arrow
Version:
A web component for creating custom arrow HTML elements.
214 lines (187 loc) • 6.57 kB
JavaScript
export class Customarrow extends HTMLElement {
constructor() {
super();
this.tailWidth = 0.5;
this.tailLength = 0.5;
this.arrowRotation = 0;
this.tailContraction = 0;
this.peakCollapse = 0;
this.unClosed = false;
this.scaleFactor = 1;
this.storedTailLength = this.tailLength;
}
static get observedAttributes() {
return [
"l",
"w",
"tail-l",
"tail-w",
"peak-l",
"rot",
"tail-cont",
"peak-coll",
"unclosed",
"scale"
];
}
disconnectedCallback() {
this.removeChild(this.querySelector("svg"));
this.observer.disconnect();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "w") {
this.aWidth = parseFloat(newValue);
} else if (name === "l") {
this.aLength = parseFloat(newValue);
} else if (name === "tail-w") {
this.tailWidth = parseFloat(newValue) / 100;
} else if (name === "tail-l") {
this.tailLength = parseFloat(newValue) / 100;
this.storedTailLength = this.tailLength;
} else if (name === "rot") {
let rot = parseFloat(newValue);
if (!isNaN(rot)) {
this.arrowRotation = rot < 0 ? rot + 360 : rot;
} else {
this.arrowRotation = 0;
}
} else if (name === "tail-cont") {
this.tailContraction = parseFloat(newValue) / 100;
} else if (name === "peak-coll") {
this.peakCollapse = parseFloat(newValue) / 100;
} else if (name === "unclosed") {
if (newValue === "false") {
this.unClosed = false;
} else {
this.unClosed = true;
}
} else if (name === "peak-l") {
this.peakLength = parseFloat(newValue);
} else if (name === "scale") {
this.scaleFactor = parseFloat(newValue);
}
if (!isNaN(this.peakLength)) {
this.tailLength = 1 - this.peakLength / this.aLength;
} else {
this.tailLength = this.storedTailLength;
}
if (!this.arrow) {
return;
}
this.removeChild(this.querySelector("svg"));
this._render();
}
_render() {
this.appendChild(this._generateArrow());
this.arrow = this.querySelector("#arrow");
this._setStyle();
}
connectedCallback() {
this._render();
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: false, subtree: false };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer, ctx) {
for (let mutation of mutationsList) {
if (mutation.type === "attributes") {
if (
ctx.strokeWidth !== parseFloat(window.getComputedStyle(ctx).strokeWidth)
) {
ctx.removeChild(ctx.querySelector("svg"));
ctx._render();
}
}
}
};
// Create an observer instance linked to the callback function
this.observer = new MutationObserver((x, y) => callback(x, y, this));
// Start observing the target node for configured mutations
this.observer.observe(this, config);
}
_setStyle() {
this.arrow.style.transform = "rotate(" + this.arrowRotation + "deg)";
//this.arrow.style.transformOrigin = "0% 50%";
this.arrow.style.position = "absolute";
let styleDisplay = window.getComputedStyle(this).display;
if (styleDisplay !== "flex" && styleDisplay !== "inline-flex" && styleDisplay !== "none") {
this.style.display = "flex";
}
this.style.justifyContent = "center";
this.style.alignItems = "center";
}
_generateArrow() {
this.svgStrokeWidth = parseFloat(window.getComputedStyle(this).strokeWidth);
let strokeWidthCompensation = this.svgStrokeWidth * 0.5;
let strokeWidthFactorY = 1 - strokeWidthCompensation / (this.aWidth / 2);
let strokeWidthFactorX = 1 - (this.svgStrokeWidth * this.tailLength) / this.aLength;
let tailTopLeftY = strokeWidthCompensation + (this.aWidth / 2) * (1 - this.tailWidth) * strokeWidthFactorY;
let tailBottomLeftY =
this.aWidth - (this.aWidth / 2) * (1 - this.tailWidth) * strokeWidthFactorY - strokeWidthCompensation;
let distanceBetweenTailLeftYs = tailBottomLeftY - tailTopLeftY;
let tailTopRightY = tailTopLeftY + 0.5 * distanceBetweenTailLeftYs * this.tailContraction;
let tailBottomRightY = tailBottomLeftY - 0.5 * distanceBetweenTailLeftYs * this.tailContraction;
let tailRightX = this.aLength * strokeWidthFactorX - this.aLength * (1 - this.tailLength);
let angle =
Math.PI / 2 -
Math.atan2(
this.tailWidth * this.svgStrokeWidth + this.aWidth / 2,
this.aLength - tailRightX + this.tailWidth * this.svgStrokeWidth
);
let cathetus = Math.tan(angle) * tailTopRightY;
let coord_1_x = strokeWidthCompensation;
let coord_1_y = tailTopLeftY;
let coord_2_x = tailRightX + strokeWidthCompensation + this.peakCollapse * (cathetus - this.svgStrokeWidth);
let coord_2_y = tailTopRightY;
let coord_3_x = tailRightX + strokeWidthCompensation;
let coord_3_y = strokeWidthCompensation;
let coord_4_x = this.aLength - strokeWidthCompensation;
let coord_4_y = this.aWidth / 2;
let coord_5_x = tailRightX + strokeWidthCompensation;
let coord_5_y = this.aWidth - strokeWidthCompensation;
let coord_6_x = tailRightX + strokeWidthCompensation + this.peakCollapse * (cathetus - this.svgStrokeWidth);
let coord_6_y = tailBottomRightY;
let coord_7_x = strokeWidthCompensation;
let coord_7_y = tailBottomLeftY;
let uri = "http://www.w3.org/2000/svg";
let svg = document.createElementNS(uri, "svg");
svg.setAttributeNS(null, "id", "arrow");
svg.setAttributeNS(null, "width", this.aLength * this.scaleFactor);
svg.setAttributeNS(null, "height", this.aWidth * this.scaleFactor);
svg.setAttributeNS(null, "viewBox", `0 0 ${this.aLength} ${this.aWidth}`);
let path = document.createElementNS(uri, "path");
let pathCoords =
"M" +
coord_1_x +
" " +
coord_1_y +
" L" +
coord_2_x +
" " +
coord_2_y +
" L" +
coord_3_x +
" " +
coord_3_y +
" L" +
coord_4_x +
" " +
coord_4_y +
" L" +
coord_5_x +
" " +
coord_5_y +
" L" +
coord_6_x +
" " +
coord_6_y +
" L" +
coord_7_x +
" " +
coord_7_y +
(this.unClosed ? "" : " Z");
path.setAttributeNS(null, "d", pathCoords);
svg.appendChild(path);
return svg;
}
}
customElements.define("wc-arrow", Customarrow);