striped-background
Version:
Generate customizable striped backgrounds using CSS gradients
107 lines (106 loc) • 4.3 kB
JavaScript
;
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`,
};
}