UNPKG

striped-background

Version:

Generate customizable striped backgrounds using CSS gradients

107 lines (106 loc) 4.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = stripedBackground; // https://developer.mozilla.org/en-US/docs/Web/CSS/angle#units const unitToHalfCircleValue = { deg: 180, grad: 200, rad: Math.PI, turn: 0.5, }; function parseStringAngle(angle) { var _a; const value = Number.parseFloat(angle); if (!Number.isFinite(value)) { throw new Error(`Unable to parse angle: ${angle}`); } const regexResult = /[^A-z](deg|grad|rad|turn)?\s*$/.exec(angle); if (!regexResult) { throw new Error(`Unable to parse angle: ${angle}`); } return [value, (_a = regexResult[1]) !== null && _a !== void 0 ? _a : "deg"]; } function normalizeAngle(angle) { const [value, unit] = typeof angle === "string" ? parseStringAngle(angle) : [angle, "deg"]; return [clampAngle(value, unit), unit]; } function convertAngle(value, from, to) { return (value * unitToHalfCircleValue[to]) / unitToHalfCircleValue[from]; } // Clamp to -180..180 degrees range. function clampAngle(angle, unit) { const halfCircle = unitToHalfCircleValue[unit]; const fullCircle = halfCircle + halfCircle; let res = angle % fullCircle; if (res > halfCircle) { res = res - fullCircle; } if (res < -halfCircle) { res = res + fullCircle; } return res; } function calcBgDimensions(patternLength, angleInRadians) { // 1px width is enough as the pattern lines are horizontal if (Math.abs(angleInRadians) === 0 || Math.abs(angleInRadians) === Math.PI) { return [1, patternLength]; } // Same for the height when lines are vertical if (Math.abs(angleInRadians) === Math.PI / 2) { return [patternLength, 1]; } return [ Math.abs(patternLength / Math.sin(angleInRadians)), Math.abs(patternLength / Math.cos(angleInRadians)), ]; } // 6 should be enough: Chrome rounds to 6 places automatically and everything looks good. // Used primarily for improved tests readability. const cssPrecision = 6; function round(value) { return Number(value.toPrecision(cssPrecision)); } function angleIsMultipleOf45Degrees(value, unit) { return round(value) % round(unitToHalfCircleValue[unit] / 4) === 0; } /** * @param pattern Array of tuples * @param angle Pattern rotation angle. * Number or numeric string will be treated as degrees. * String can contain CSS angle unit, e.g. `45deg`, `50grad`, `0.7854rad`, `0.125turn` * @param offset Pattern offset, in pixels */ function stripedBackground(pattern, angle = 45, offset = 0) { // Nothing to apply if (pattern.length === 0) { return {}; } // Solid single color background if (pattern.length === 1) { return { backgroundColor: pattern[0][0], }; } const [angleNormalized, unit] = normalizeAngle(angle); const angleRadians = convertAngle(angleNormalized, unit, "rad"); // When angle is multiple of 45 degrees (1/8 of full circle), transition smoothing is not necessary. const smoothing = angleIsMultipleOf45Degrees(angleNormalized, unit) ? 0 : 0.5; const colorStops = []; let currentOffset = 0; for (let i = 0; i < pattern.length * 2 - 1; i++) { currentOffset += pattern[i % pattern.length][1]; // We need clear edges between colors, so each color starts exactly where previous ends (plus smoothing margin). colorStops.push(`${pattern[i % pattern.length][0]} ${currentOffset - smoothing}px`, `${pattern[(i + 1) % pattern.length][0]} ${currentOffset + smoothing}px`); } const patternLength = pattern.reduce((acc, [, l]) => acc + l, 0); const offsetNorm = offset % patternLength, offsetX = offsetNorm * Math.sin(angleRadians), offsetY = offsetNorm * Math.cos(angleRadians); const [bgWidth, bgHeight] = calcBgDimensions(patternLength, angleRadians); return { // Normalize angle value, but do not apply rounding and unit conversion to keep original precision. backgroundImage: `linear-gradient(${angleNormalized}${unit}, ${colorStops.join(", ")})`, backgroundPosition: `top ${round(-offsetY)}px left ${round(offsetX)}px`, backgroundSize: `${round(bgWidth)}px ${round(bgHeight)}px`, }; }