我总是发现动态创建SVG内容,并过滤一个痛苦和混乱的过程。
setAttribute和setAttribute访问SVG属性会使代码变得一团糟。clip-path和clipPathUnit是正确的,而clipPath和clip-path-unit是不正确的。由于这些和其他原因,我通常不使用SVG,但是由于HTML CanvasRenderingContext2D现在对filter属性有很好的支持,填补了API中一个急需的漏洞,我发现我自己写SVG的内容越来越多。
因此,在SVG代码中隐藏了一个特别讨厌的bug之后,我决定编写以下代码。
const createSVG = (()=>{
/* This code uses some abreviations
str is string
arr is array
num is number
prop is property
props is properties
2 for conversion eg str2Num is string to number
*/
var id = 0;
var units = "";
const svgNamespace = "http://www.w3.org/2000/svg";
const transformTypes = {read : "read", write : "write"};
const transformPropsName = "accent-height,alignment-baseline,arabic-form,baseline-shift,cap-height,clip-path,clip-rule,color-interpolation,color-interpolation-filters,color-profile,color-rendering,dominant-baseline,enable-background,fill-opacity,fill-rule,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,horiz-adv-x,horiz-origin-x,image-rendering,letter-spacing,lighting-color,marker-end,marker-mid,marker-start,overline-position,overline-thickness,panose-1,paint-order,pointer-events,rendering-intent,shape-rendering,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,text-anchor,text-decoration,text-rendering,underline-position,underline-thickness,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,vert-adv-y,vert-origin-x,vert-origin-y,word-spacing,writing-mode,x-height";
const unitPropsNames ="width,height,x,y,z,x1,x2,y1,y2,cx,cy,rx,ry,r,accentHeight,alignmentBaseline,baselineShift,capHeight,fontSize,fontSizeAdjust,overlinePosition,overlineThickness,strikethroughPosition,strikethroughThickness,strokeWidth,underlinePosition,underlineThickness,vertOriginX,vertOriginY,wordSpacing,xHeight";
/* Transform helper functions */
const onlyArr2Str = (value, points = false) => {
if (points) {
if (Array.isArray(value)) {
return value.map(point => Array.isArray(point) ? point.join(",") : point).join(" ");
}
return value;
}
return Array.isArray(value) ? value.join(" ") : value
}
/* Value transform functions */
const arr2Str = value => onlyArr2Str(value);
const str2NumArr = value => value.split(" ").map(value => Number(value));
const unitStr2Num = value => Number(value.replace(/[a-z]/gi, ""));
const str2Num = value => Number(value);
const str2NumOrStr = value => isNaN(value) ? value : Number(value);
const num2UnitStr = value => value + units;
const num2Percent = value => value + "%";
const pointArr2Str = value => onlyArr2Str(value, true);
const url2Str = value => value.replace(/url\(#|)/g, "");
const ref2Url = value => {
if (typeof value === "string") {
if (value.indexOf("url(#") > -1) { return value }
return `url(#${value})`;
}
if (value.isPSVG) {
if (value.node.id) { return `url(#${value.node.id})` }
value.node.id = "PSVG_ID_"+ (id ++);
return `url(#${value.node.id})`;
}
return value;
};
const str2PointArr = value => value.split(" ").map(point => {
point = point.split(",");
point[0] = Number(point[0]);
point[1] = Number(point[1]);
return point;
});
/* property value transforms `read` from SVG `write` to SVG */
const transforms = {
read : {
offset : unitStr2Num,
points : str2PointArr,
filter : url2Str,
clipPath : url2Str,
stdDeviation: str2Num,
dx : str2Num,
dy : str2Num,
tableValues : str2NumArr,
values : str2NumArr,
kernelMatrix: str2NumArr,
viewbox : str2NumArr,
_default : str2NumOrStr,
},
write : {
points : pointArr2Str,
offset : num2Percent,
filter : ref2Url,
clipPath : ref2Url,
tableValues : arr2Str,
values : arr2Str,
kernelMatrix: arr2Str,
viewbox : arr2Str,
_default(value) { return value },
},
}
/* Assign additional unit value transforms */
unitPropsNames.split(",").forEach((propName) => {
transforms.read[propName] = unitStr2Num;
transforms.write[propName] = num2UnitStr;
});
/* Create property name transform lookups */
const propNodeNames = transformPropsName.split(",");
const propScriptNames = transformPropsName.replace(/-./g, str => str[1].toUpperCase()).split(",");
/* returns transformed `value` of associated property `name` depending on `[type]` default write*/
function transform(name, value, type = transformTypes.write) {
return transforms[type][name] ? transforms[type][name](value) : transforms[type]._default(value);
}
/* returns Transformed JavaScript property name as SVG property name if needed. EG "fillRule" >> "fill-rule" */
function propNameTransform(name) {
const index = propScriptNames.indexOf(name);
return index === -1 ? name : propNodeNames[index];
}
/* node creation function returned as the interface instanciator of the node proxy */
/* type String representing the node type.
props optional Object containing node properties to set
returns a proxy holding the node */
const createSVG = (type, props = {}) => {
const PSVG = (()=>{ // PSVG is abreviation for Practical SVG
const node = document.createElementNS(svgNamespace, type);
const set = (name, value) => node.setAttribute(propNameTransform(name), transform(name, value));
const get = (name, value) => transform(name, node.getAttribute(propNameTransform(name)), transformTypes.read);
const svg = {
isPSVG : true,
nodeType : type,
node : node,
set units(postFix) { units = postFix },
get units() { return units },
};
const proxyHandler = {
get(target, name) { return svg[name] !== undefined ? target[name] : get(name) },
set(target, name, value) {
if (value !== null && typeof value === "object" && value.isPSVG) {
node.appendChild(value.node);
target[name] = value;
return true;
}
set(name,value);
return true;
},
};
return new Proxy(svg, proxyHandler);
})();
Object.keys(props).forEach(key => PSVG[key] = props[key]);
return PSVG;
}
return createSVG;
})();
export default createSVG;我的SVG经验是低的,我根本不确定这是否安全,甚至对野外实用。
如有任何意见、建议、警告或改进,将不胜感激。
它创建XML节点并返回保存该节点的对象的代理。代理get和set处理程序很难在JavaScript友好格式和JavaScript格式之间转换属性名称和属性值,并根据属性类型执行正确的操作。
"20px"变成20svg.circle.filter = svg.blurFilter // 的blurfilter id。svg.circle = createSVG("circle");一个新的圆节点被附加到svg节点。svg.circle.r正确地访问圆节点的radius属性。示例用法
//=====================================================
// Create a node.
const svg = createSVG("svg",{width : 100, height : 100});
//=====================================================
// Add a node
const svg.circle = createSVG("circle");
//=====================================================
// Add and set property
svg.circle.cx = 50; // Note that default units is px
svg.circle.cx = 50;
svg.circle.r = 30;
svg.circle.fill = "Green";
svg.circle.stroke = "black";
//=====================================================
// Transforming property name
svg.circle.strokeWidth = 3; // SVG circle property name "stroke-width"
// XML result of circle
//
//=====================================================
// Modify a property
svg.circle.r += 10;
//=====================================================
// Array value transformation
svg.polygon = createSVG("polygon");
// array to string transform
// "0,0 100,0 100,100 0,100";
svg.polygon.points = [[0,0],[100,0],[100,100],[0,100]];
// string to array transform
const pointsArray = svg.polygon.points;
//=====================================================
// node access
svg.text = createSVG("text");
svg.text.node.textContent = "Hi World";
//=====================================================
// Adding to DOM
document.appendChild(svg.node); 下面的代码片段包含一个示例使用。创建一个SVG节点,添加到DOM,小延迟然后更新节点属性,添加其他节点,并启动Javascript控制的动画。
"use strict";
/* Example usage */
setTimeout(()=>{
const width = 100;
const height = 100;
const resizeBy = 10;
const pathPoints = [[0,0], [100,0], [50,50], [100,100], [0,100], [50,50], [0,0]];
const pathStyle = {points : pathPoints, fill : "orange", stroke : "black", strokeWidth : 3 };
// =======================================================================
// createSVG takes two arguments
// The node type as a string
// optional object containing normalized properties and values
const svg = createSVG("svg", {width : width, height : height});
// create a polygon node
svg.polygon = createSVG("polygon", pathStyle);
// =======================================================================
// add svg node to DOM
exampleSVG.appendChild(svg.node);
XMLResult.textContent = exampleSVG.innerHTML;
// =======================================================================
// Two seconds then change some properties and add new nodes
setTimeout(() => {
infoElement.textContent = "SVG properties updated and nodes added. Javascript animation";
// resize SVG
var extraSize = (svg.polygon.strokeWidth + 2) * 2;
svg.width += resizeBy + extraSize; // The proxy get converts string to NUMBER and
svg.height += resizeBy + extraSize; // the converts back to string and append units if used
// The path.points as a SVG string is converted back to array of points
// the array is assigned to point and then converted back to a points string
svg.polygon.points = svg.polygon.points.map(point => (point[0] += 10, point[1] += 10, point));
// get polygon node "stroke-width" converts to Number add 2 and sets new value
svg.polygon.strokeWidth += 2;
// change the fill.
svg.polygon.fill = "Green";
// Append a new circle object to the svg
svg.circle = createSVG("circle");
svg.circle.cx = svg.width / 2;
svg.circle.cy = svg.height / 2;
svg.circle.r = Math.min(svg.width, svg.height) / 3;
svg.circle.fill = "orange";
// Example of setting node content
svg.text = createSVG("text",{x : 25, y : 20, fontFamily : "Verdana", fontSize : "10", fill : "white"});
// Each PSVG object has node property that is the actual XML node and its properties
// can be set directly
svg.text.node.textContent = "Some text.";
// Animate circle
requestAnimationFrame(animate);
},2000);
//=========================================================================
/* JAVASCRIPT driven animation of SVG */
function animate(time){
var x = svg.width / 2
var y = svg.height / 2
var rad = Math.cos(time / 2000) * Math.min(x,y) * (1/4) + Math.min(x,y) * (1/2);
svg.circle.r = rad;
x += Math.cos(time / 1000) * rad * (1/3);
y += Math.sin(time / 1000) * rad * (1/3);
svg.circle.cx = x;
svg.circle.cy = y;
XMLResult.textContent = exampleSVG.innerHTML;
requestAnimationFrame(animate);
}
},0)
/* =============================================================================
createSVG is module for review
===============================================================================*/
const createSVG = (()=>{
/* This code uses some abreviations
str is string
arr is array
num is number
prop is property
props is properties
2 for conversion eg str2Num is string to number
*/
var id = 0;
var units = "";
const svgNamespace = "http://www.w3.org/2000/svg";
const transformTypes = {read : "read", write : "write"};
const transformPropsName = "accent-height,alignment-baseline,arabic-form,baseline-shift,cap-height,clip-path,clip-rule,color-interpolation,color-interpolation-filters,color-profile,color-rendering,dominant-baseline,enable-background,fill-opacity,fill-rule,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,horiz-adv-x,horiz-origin-x,image-rendering,letter-spacing,lighting-color,marker-end,marker-mid,marker-start,overline-position,overline-thickness,panose-1,paint-order,pointer-events,rendering-intent,shape-rendering,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,text-anchor,text-decoration,text-rendering,underline-position,underline-thickness,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,vert-adv-y,vert-origin-x,vert-origin-y,word-spacing,writing-mode,x-height";
const unitPropsNames ="width,height,x,y,z,x1,x2,y1,y2,cx,cy,rx,ry,r,accentHeight,alignmentBaseline,baselineShift,capHeight,fontSize,fontSizeAdjust,overlinePosition,overlineThickness,strikethroughPosition,strikethroughThickness,strokeWidth,underlinePosition,underlineThickness,vertOriginX,vertOriginY,wordSpacing,xHeight";
/* Transform helper functions */
const onlyArr2Str = (value, points = false) => {
if (points) {
if (Array.isArray(value)) {
return value.map(point => Array.isArray(point) ? point.join(",") : point).join(" ");
}
return value;
}
return Array.isArray(value) ? value.join(" ") : value
}
/* Value transform functions */
const arr2Str = value => onlyArr2Str(value);
const str2NumArr = value => value.split(" ").map(value => Number(value));
const unitStr2Num = value => Number(value.replace(/[a-z]/gi, ""));
const str2Num = value => Number(value);
const str2NumOrStr = value => isNaN(value) ? value : Number(value);
const num2UnitStr = value => value + units;
const num2Percent = value => value + "%";
const pointArr2Str = value => onlyArr2Str(value, true);
const url2Str = value => value.replace(/url\(#|)/g, "");
const ref2Url = value => {
if (typeof value === "string") {
if (value.indexOf("url(#") > -1) { return value }
return `url(#${value})`;
}
if (value.isPSVG) {
if (value.node.id) { return `url(#${value.node.id})` }
value.node.id = "PSVG_ID_"+ (id ++);
return `url(#${value.node.id})`;
}
return value;
};
const str2PointArr = value => value.split(" ").map(point => {
point = point.split(",");
point[0] = Number(point[0]);
point[1] = Number(point[1]);
return point;
});
/* property value transforms `read` from SVG `write` to SVG */
const transforms = {
read : {
offset : unitStr2Num,
points : str2PointArr,
filter : url2Str,
clipPath : url2Str,
stdDeviation: str2Num,
dx : str2Num,
dy : str2Num,
tableValues : str2NumArr,
values : str2NumArr,
kernelMatrix: str2NumArr,
viewbox : str2NumArr,
_default : str2NumOrStr,
},
write : {
points : pointArr2Str,
offset : num2Percent,
filter : ref2Url,
clipPath : ref2Url,
tableValues : arr2Str,
values : arr2Str,
kernelMatrix: arr2Str,
viewbox : arr2Str,
_default(value) { return value },
},
}
/* Assign additional unit value transforms */
unitPropsNames.split(",").forEach((propName) => {
transforms.read[propName] = unitStr2Num;
transforms.write[propName] = num2UnitStr;
});
/* Create property name transform lookups */
const propNodeNames = transformPropsName.split(",");
const propScriptNames = transformPropsName.replace(/-./g, str => str[1].toUpperCase()).split(",");
/* returns transformed `value` of associated property `name` depending on `[type]` default write*/
function transform(name, value, type = transformTypes.write) {
return transforms[type][name] ? transforms[type][name](value) : transforms[type]._default(value);
}
/* returns Transformed JavaScript property name as SVG property name if needed. EG "fillRule" >> "fill-rule" */
function propNameTransform(name) {
const index = propScriptNames.indexOf(name);
return index === -1 ? name : propNodeNames[index];
}
/* node creation function returned as the interface instanciator of the node proxy */
/* type String representing the node type.
props optional Object containing node properties to set
returns a proxy holding the node */
const createSVG = (type, props = {}) => {
const PSVG = (()=>{ // PSVG is abreviation for Practical SVG
const node = document.createElementNS(svgNamespace, type);
const set = (name, value) => node.setAttribute(propNameTransform(name), transform(name, value));
const get = (name, value) => transform(name, node.getAttribute(propNameTransform(name)), transformTypes.read);
const svg = {
isPSVG : true,
nodeType : type,
node : node,
set units(postFix) { units = postFix },
get units() { return units },
};
const proxyHandler = {
get(target, name) { return svg[name] !== undefined ? target[name] : get(name) },
set(target, name, value) {
if (value !== null && typeof value === "object" && value.isPSVG) {
node.appendChild(value.node);
target[name] = value;
return true;
}
set(name,value);
return true;
},
};
return new Proxy(svg, proxyHandler);
})();
Object.keys(props).forEach(key => PSVG[key] = props[key]);
return PSVG;
}
return createSVG;
})();body {
font-family : arial;
}
#XMLResult {
font-family : consola;
font-size : 12px;
}SVG node added. In two seconds is modified.
The XML;svg.a = createSVG("a"); svg.a = createSVG("a");创建两个节点,但只创建一个引用。发布于 2018-06-01 19:22:43
我不能声称自己在SVG创建方面做了很多事情,但是我可以为创建SVG项的代码提供一些评论点。看起来,您已经做了很多工作来抽象出满足您的需求所需的通用SVG代码。对于没有重新分配的值,可以很好地使用const,对于其他变量,可以使用let。而且没有嵌套的三值算子是很好的。
有很多行检查参数是否是数组.例如,onlyArr2Str检查value是否为数组,如果是数组,则检查每个元素是否为数组(在.map()回调中)。函数似乎只调用两次(至少用示例代码调用),每次value都是一个数组。在某些情况下,情况并非如此?
只需将str2Num替换为Number即可--类似地,onlyArr2Str()中的这一行:
const str2NumArr =值=> value.split(“”).map(值=>数( value ));
可简化为:
const str2NumArr = value => value.split(" ").map(Number);为什么不为animate、transform和propNameTransform使用箭头函数语法?目标是将变量的范围保存在内部吗?
可以使用svg缩短代理返回的对象的至少一个属性(即PSVG()中的速记财产名称 ),因为属性名称与变量名称相同:
const svg = {
isPSVG : true,
nodeType : type,
node,如果对nodeType函数的第一个参数(即type)作了相应的重命名,也可能是如此。
这一行:
Object.keys(props).forEach(key => PSVG = props);
如果这是错误的,请纠正我,但这似乎可以通过使用Object.assign()来简化:
Object.assign(PSVG, props);https://codereview.stackexchange.com/questions/186411
复制相似问题