我使用过乳胶,尤其是tikz。使用这个我能够创建如下所示的图像。
以下短代码用于创建图像。
function hasBirthday(name){
friends.forEach(function(person){
if(name === person.name){
person.age += 1;
}return person.age;
});
}
我在过去几个小时内尝试使用'HTMLæ,'CSS'和'Javascript'重新创建相同的图像。我使用'canvas'元素绘制线条,但是我遇到了一系列问题,如下图所示
使用以下代码制作。我尽我所能来尽量减少代码。代码可以在帖子的底部找到。代码有以下问题
可扩展性。图像中的文本与页面的“正文”中的文本不同。
图像隐藏了正文中的其余部分
将文字放在图中以外是硬编码的
最后一个小问题是未绘制列表中的第一个元素
我想解决上述问题,但我不确定如何继续。我再次与使用canvas的想法结合在一起(可以使用节点和元素来完成更好的结果)。但是,输出应尽可能地模仿第一个图像。
\documentclass[tikz]{standalone}
\begin{document}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{backgrounds}
\begin{tikzpicture}[background rectangle/.style={fill=black},
show background rectangle]
\def\pages{
Home,
Events,
Pictures,
Video,
Contact,
About,
Map
}
\def\ngon{7}
\node[regular polygon,regular polygon sides=\ngon,minimum size=3cm] (p) {};
\foreach\page [count=\x] in \pages{\node[color=white, shift={(\x*360/7+35:0.4)}] (p\x) at (p.corner \x){\page};}
\foreach\i in {1,...,\numexpr\ngon-1\relax}{
\foreach\j in {\i,...,\x}{
\draw[thin, orange, dashed] (p\i) -- (p\j);
}
}
\end{tikzpicture}
\end{document}
答案 0 :(得分:2)
首先我会说使用javascript会比使用像Latex这样的符号表示语言更长。它旨在以最小的麻烦进行图形表示。使其工作的实际代码库是实质性的,但对于普通用户来说是隐藏的。
由于画布的内容存储在DOM中,因此最好在DOM中存储尽可能多的信息,颜色,字体等都可以存储在元素的数据集中。
为此,我将设置放在有序列表中。它包含所有设置,但渲染功能中还有一组默认设置。元素数据集将覆盖默认值,或者您无法添加任何数据集属性,并让它们都使用默认值。
在下面的例子中,我只进行了最少的审查。人们倾向于在DOM中的所有内容中加上引号,因为如果表示为字符串,数字有时可能无效,我强制所有数字都为正确的类型。虽然为了安全起见,我应该检查一下它们是否确实是有效数字,其他设置也是如此。我刚认为它们的格式正确。
所有工作都在一个函数中完成,你传递查找列表和画布所需的查询字符串。然后,它使用列表项呈现到画布。
由于画布大小并不总是已知(可以通过CSS缩放),因此您需要有一些方法来指定与像素无关的大小。为此,我使用相对大小。因此,字体大小是画布大小的一部分,例如data-font-size = 16
表示字体将是画布高度的1/16。线宽相同,短划线大小是线宽的倍数。例如data-line-dash = 4
表示短划线的长度是线宽的4倍。
要使用数据集,请将属性添加到前缀为单词data-
的HTML中的元素,然后将属性名称/ s分隔为"-"
。在javascript中,您不能直接将"-"
用作变量名称的一部分(它是一个减法运算符),因此属性名称将转换为camelcase(与CSS属性相同),并存储在元素&#39中; s dataset
财产。
<!-- HTML -->
<div id="divElement" data-my-Value = "some data"></div>
<script>
// the property of divElement is available as
console.log(divElement.dataset.myValue); // output >> "some data"
</script>
画布以理想大小(在本例中为512)渲染,但转换设置为确保渲染适合画布。在这个例子中,我缩放了x和y轴)结果是图像没有固定的方面。
默认情况下,画布是透明的,但是如果您重新渲染它,我会清除它。画布下的任何东西都应该是可见的。
我首先渲染线条,然后是文本,清除文本下面的空格以删除线条。 ctx.clearRect确保画布rect是透明的。
要绘制线条,您有两个循环,从每个项目中绘制一条线到每个其他项目。您不想多次绘制一条线,因此内部循环从当前外部循环位置+ 1开始。这样可以确保一条线只渲染一条。
这个例子展示了我认为你所追求的。我已添加了大量评论,但如果您有任何疑问,请在下面的评论中提出。
我认为您希望有序列表可见。如果不使用CSS规则来隐藏它,则不会影响画布渲染。
此外,如果您通过CSS调整画布大小,则可能会出现画布分辨率和显示大小不匹配的情况。这可能导致像素模糊,并且一些高分辨率显示器会将画布像素设置为大。如果这是一个问题,那么就如何处理模糊的画布渲染和高分辨率显示(如视网膜)有很多答案。
function drawConnected(listQ, canvasQ) {
const list = document.querySelector(listQ);
if(list === null){
console.warn("Could not find list '" + listQ +"'");
return;
}
const canvas = document.querySelector(canvasQ);
if(canvas === null){
console.warn("Could not find canvas '" + canvasQ + "'");
return;
}
const ctx = canvas.getContext("2d");
const size = 512; // Generic size. This is scaled to fit the canvas
const xScale = canvas.width / size;
const yScale = canvas.height / size;
// get settings or use dsefault
const settings = Object.assign({
fontSize : 16,
lineWidth : 128,
lineDash : 4,
textColor : "White",
lineColor : "#F90", // orange
startAngle : -Math.PI / 2,
font : "arial",
}, list.dataset);
// calculate relative sizes. convert deg to randians
const fontSize = size / Number(settings.fontSize) | 0; // (| 0 floors the value)
const lineWidth = size / Number(settings.lineWidth) | 0;
const lineDash = lineWidth * Number(settings.lineDash);
const startAngle = Number(settings.startAngle) * Math.PI / 180; // -90 deg is top of screen
// get text in all the list items
const items = [...list.querySelectorAll("li")].map(element => element.textContent);
// Set up the canvas
// Scale the canvas content to fit.
ctx.setTransform(xScale,0,0,yScale,0,0);
ctx.clearRect(0,0,size,size); // clear as canvas may have content
ctx.font = fontSize + "px " + settings.font;
// align text to render from its center
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// set the line details
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.setLineDash([lineDash, lineDash]);
// need to make room for text so calculate all the text widths
const widths = [];
for(let i = 0; i < items.length; i ++){
widths[i] = ctx.measureText(items[i]).width;
}
// use the max width to find a radius that will fit all text
const maxWidth = Math.max(...widths);
const radius = (size/2 - maxWidth * 0.6);
// this function returns the x y position on the circle for item at pos
const getPos = (pos) => {
const ang = pos / items.length * Math.PI * 2 + startAngle;
return [
Math.cos(ang) * radius + size / 2,
Math.sin(ang) * radius + size / 2
];
};
// draw lines first
ctx.strokeStyle = settings.lineColor;
ctx.beginPath();
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
for(let j = i+1; j < items.length; j ++){
const [x1,y1] = getPos(j);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
}
}
ctx.stroke();
// draw text
ctx.fillStyle = settings.textColor;
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
ctx.clearRect(x - widths[i] * 0.6, y - fontSize * 0.6, widths[i] * 1.2, fontSize * 1.2);
ctx.fillText(items[i],x,y);
}
// restore default transform;
ctx.setTransform(1,0,0,1,0,0);
}
// draw the diagram with selector query for ordered list and canvas
drawConnected("#poly","#polygon");
&#13;
* {
margin: 0;
padding: 0;
color:white;
background:black;
}
canvas {
display: block;
}
html,
body {
font-family : arial;
width: 100%;
height: 100%;
margin: 0px;
border: 0;
display: block;
}
&#13;
<canvas id="polygon" width = "256" height = "256"></canvas>
<h2>more space</h2>
<ol id="poly"
data-font-size = 16
data-line-width = 128
data-line-dash = 2
data-text-color = "white"
data-line-color = "#F80"
data-start-angle = "-90"
data-font = "arial"
>
<li>About</li>
<li>Home</li>
<li>Pictures</li>
<li>Video</li>
<li>Events</li>
<li>Map</li>
<li>Apply?</li>
<li>Recepies</li>
</ol>
&#13;