Related
I am trying to implement barcode scanner viewFinder and I want to use svg icon to make it look nice, but I have a problem with forcing the path element inside the svg to take up the full svg width and height. I am using react native and to generate icon i use SVGR https://react-svgr.com/playground/?native=true&typescript=true in the scan handler I set the dimensions of the svg like so:
const handleBarCodeScanned = ({ type, data, bounds }: BarCodeEvent) => {
if (!bounds) return;
const { origin, size } = bounds;
setX(origin.x);
setY(origin.y);
setWidth(size.width);
setHeight(size.height);
};
and the I ise them inside the svg which looks like so
import * as React from "react";
import Svg, { SvgProps, Path } from "react-native-svg";
export const ViewFinder = (props: SvgProps & { top: number; left: number }) => {
const { width, height, top, left } = props;
return (
<Svg
width={width}
height={height}
style={{
borderColor: "green",
borderWidth: 2,
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
}}
fill="none"
stroke="green"
preserveAspectRatio="none"
viewBox={`0 0 ${width} ${height}`}
>
<Path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></Path>
<Path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></Path>
</Svg>
);
};
original icon is a featerIcons crop icon https://feathericons.com/ and the original code of the icon is:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crop"><path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></path><path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></path></svg>
as you can see I set the border color and borderWidth on the svg itself, and it scales to fit the container so here everything seems to be ok. I have viewBox and preserveAspectRatio set up its just the inner path not scaling with the svg, and it is not just this icon I have tries several and the issue is still this same so there must be something wrong with my understanding of svg.
Thanks a lot for any help.
Normally a viewBox would be 4 fixed numbers, i.e. unrelated to width and height. That should give you the result you want.
Your content doesn't change in size so your viewBox shouldn't change either.
I am trying to figure out why getBBox() for tspan element of a svg does not return the dimension.
To demonstrate this with an example, if I run BBox on both tsp1 and rect1, it returns the correct dimension for rect1 but not for tsp1
var tsp = document.getElementById('tsp1');
var tspBBox = tsp.getBBox();
var rect = document.getElementById('rect1');
var rectBBox = rect.getBBox();
console.log(tspBBox);
console.log(rectBBox);
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<text class="t1" id="t1" font-size="20" font-family="PT Mono" text-decoration="underline">
<tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan>
</text>
<rect class="rect1" id="rect1" x="9.23" y="112.73368530273439" height="31.546314697265625" width="1" fill="orange" />
</svg>
I was expecting BBox to return the exact x and y for tsp1 but it does not.
I don't know why. I need to pass on the exact values to the succeeding class dynamically.
How can javascript return the exact dimension for the tspan element?
There are a number of methods for measuring text, and they are a bit more complex than defining a simple box. This is because with the dx, dy and rotate attributes, each addressable character can be be positioned individually - moved and rotated in every direction. Therefore, it makes more sense to answer the question where a single character is positioned, and where, after completing one sequence, the next character would be positioned.
In your case none of the above attributes are set ( on the <tspan> or <text> element). In this case is is possible to retrieve the start position of the <tspan> with .getStartPositionOfChar(0) and the horizontal width with .getComputedTextLength().* The height according to the font metrics is the same for all characters in the tspan, so it is enough to return one .getExtentOfChar(0) - 0 refers to the first character within the sequence of addressable characters.
As chrwahl pointed out in his answer, the start position refers to the font-specific baseline and normally will not be identical to the top left corner of a bounding box.
*There is a subtle trick here: if the letter-spacing or word-spacing CSS properties were defined, the "length" returned would not only return the width from the start of the first character to the end of the last, but also would add (or subtract) a spacing value that is defined after the end of the string. In other words: despite its name, the method returns the relative horizontal start position of the next character after the string examined.
var tsp = document.getElementById('tsp1');
var tspPos = tsp.getStartPositionOfChar(0);
console.log('start position', tspPos.x, tspPos.y);
console.log('horizontal advance', tsp.getComputedTextLength());
console.log('vertical extent', tsp.getExtentOfChar(0).height);
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<text class="t1" id="t1" font-size="20" font-family="PT Mono" text-decoration="underline">
<tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan>
</text>
</svg>
It is all about the dominant-baseline. So, there is a differences between where the text is placed according to the dominant-baseline and the box that the text takes up. The value text-before-edge will place the text according to the upper left corner of the box.
var tsp = document.getElementById('tsp1');
var tspBBox = tsp.getBBox();
var rect = document.getElementById('rect1');
var rectBBox = rect.getBBox();
console.log('tspBBox', tspBBox.x, tspBBox.y);
console.log('rectBBox', rectBBox.x, rectBBox.y);
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 100 400 200">
<text class="t1" id="t1" font-size="20" font-family="PT Mono"
text-decoration="underline" dominant-baseline="text-before-edge">
<tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan>
</text>
<rect class="rect1" id="rect1" x="9.23" y="112.73368530273439" height="31.546314697265625" width="1" fill="orange" />
</svg>
Helo I have a problem. I am using npm package which provides pack of weather icons. The problem is the SVG box is big and it breaks positioning that is a lot of white space on website, it does not look nice. Is negative margin the way>
import {wiCloudy} from 'weather-icons-react'
const WeatherDataBox = () => {
return (
icon=<WiCloudy size={256} />
)}
<svg stroke="currentColor" fill="currentColor"
stroke-width="0" viewBox="0 0 30 30"
attr="[object Object]" size="256" height="256" width="256">
<path>*long code*</path>
</svg>
I changed viewbox and it reduced empty background
let ICON_SIZE = 256
let VIEWBOX = "5 5 20 20"
<WiCloudy viewBox={VIEWBOX} size={ICON_SIZE} />
I'm trying to place a text onto a path with svg.js
Here is my fiddle:
https://jsfiddle.net/Byteschmiede/ytz67egn/1/
var draw = SVG().addTo('body').size(500, 500)
draw.svg(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" viewBox="0 0 4725 2363" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path id="text1326" d="M2362.2,952.109C2548.36,773.615 2727.44,622.128 2899.46,497.647C3071.47,370.823 3216.39,286.272 3334.21,243.993C3452.02,201.721 3580.45,180.583 3719.47,180.58C4011.66,180.583 4247.3,278.051 4426.39,472.986C4607.82,665.577 4698.54,899.267 4698.55,1174.06C4698.54,1361.95 4658.48,1535.75 4578.37,1695.46C4498.25,1855.16 4383.97,1976.12 4235.52,2058.32C4089.42,2140.53 3920.94,2181.63 3730.08,2181.62C3482.66,2181.63 3265.87,2128.78 3079.72,2023.09C2895.92,1917.4 2656.75,1717.77 2362.2,1424.19C2055.87,1727.16 1811.99,1929.15 1630.55,2030.14C1449.11,2131.13 1238.21,2181.63 997.866,2181.62C691.537,2181.63 452.365,2086.51 280.351,1896.27C110.692,1706.03 25.862,1465.29 25.863,1174.06C25.862,901.616 115.404,667.926 294.489,472.986C475.929,278.051 712.744,180.583 1004.94,180.58C1146.32,180.583 1275.92,201.721 1393.74,243.993C1511.55,286.272 1655.29,370.823 1824.95,497.647C1996.97,622.128 2176.05,773.615 2362.2,952.109M2591.95,1170.53C2841.72,1417.14 3046.73,1585.07 3206.96,1674.32C3369.55,1761.22 3530.96,1804.67 3691.2,1804.67C3891.49,1804.67 4048.18,1745.95 4161.29,1628.52C4274.4,1508.74 4330.95,1364.3 4330.95,1195.19C4330.95,1009.65 4274.4,856.992 4161.29,737.208C4050.54,615.082 3903.27,554.017 3719.47,554.014C3615.79,554.017 3515.64,572.806 3419.04,610.382C3322.42,645.614 3206.96,709.027 3072.65,800.622C2938.33,889.873 2778.1,1013.18 2591.95,1170.53M2132.46,1170.53C1962.8,1027.27 1810.81,909.836 1676.5,818.237C1542.19,724.294 1424.37,657.357 1323.05,617.428C1221.72,577.503 1110.97,557.54 990.797,557.537C818.78,557.54 676.22,617.43 563.115,737.208C450.009,856.992 393.456,1009.65 393.457,1195.19C393.456,1324.37 422.911,1433.58 481.82,1522.83C540.729,1612.08 612.598,1681.37 697.428,1730.68C784.613,1780.01 893.006,1804.67 1022.61,1804.67C1192.27,1804.67 1357.21,1760.05 1517.45,1670.8C1677.68,1581.55 1882.68,1414.79 2132.46,1170.53" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve3" d="M294.489,472.986C475.929,278.051 712.744,180.583 1004.94,180.58C1146.32,180.583 1275.92,201.721 1393.74,243.993C1511.55,286.272 1655.29,370.823 1824.95,497.647" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve2" d="M2899.46,497.647C3071.47,370.823 3216.39,286.272 3334.21,243.993C3452.02,201.721 3580.45,180.583 3719.47,180.58C4011.66,180.583 4247.3,278.051 4426.39,472.986" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve1" d="M564.216,1310.73C609.732,1475.4 770.154,1619.22 909.116,1635.49C993.881,1645.42 1152.78,1644.6 1262.96,1605.82C1479.72,1529.53 1626.78,1408.31 1802.89,1258.49" style="fill:none;fill-rule:nonzero;stroke:rgb(177,0,52);stroke-width:1px;"/>
</svg>
`)
let textCurve1 = draw.find("#textCurve1")[0]
let textPath = textCurve1.text("Forever").font({
size: textCurve1.height()
})
.attr({
startOffset: '50%',
'text-anchor': 'middle'
})
.fill({
opacity: 0
}).stroke({
color: "#000",
width: 1
})
The red line is the target path.
The Text needs to follow the path.
As you can see, the text curve is correct but the placement is way to low on the y axis.
I'm not sure how to solve this, since there aren't much opportunities.
I tried attributes like dominant-baseline and baseline-alignment but nothing workend
Here is a picture of the svg
You can use a tspan with a dx attribute to move specific text up or down.
const text = draw.text((text) => {
text.tspan('Some Text').dx(10)
}).path(textCurve1)
I am currently trying to create a bicycle chain via SVG and javascript.
The result I am trying to create will look something like this:
The problem I am having is how to repeat a single chainlink image along the path of the chain. I want to be able to use a chainlink image (For example:
and then repeat this along the path that I define.
I know that it is trivial to repeat text along a path via SVG but is it possible to do the same with an image? The complexity comes from the fact that the chainlink will have to appear as one continuous line.
Here is what I have so far:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<svg width="700" height="500">
<path id="ChainPath" fill="none" stroke="red" d="M150 100 L400 100 C650 100 650 400 400 400 L150 400" />
<text>
<textPath href="#ChainPath" alignment-baseline="middle">
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
</textPath>
</text>
</svg>
</body>
</html>
Any help would be greatly appreciated.
Thanks
I tried to practically implement Robert Monfera's answer and found a few extra hitches. I started out computing the points of a polygon sitting, as he described, at the axes/connecting pins of the individual chain links, and all with exactly identical distance between them.
One thing that immediately strikes if you look at it is that a chain is not a smooth line. For the human eye, it might look like one, but describing the chain running around the gear as a circular arc is a bad approximation.
Look at the picture in the question. The right gear has 15 teeth. That means the angle between neighbouring chain links is 24°, and the length difference between an arc and the cord from point to point (which is the constructive length of the link) is approx. 0.8%. That doesn't sound like much, but for a link that you draw with a screen size of 50px, it's almost half a pixel. That is a difference that can be seen.
The next propostion was to use <marker> elements for the individual chain links with the origin in the center between the two pins. Since the polygon defined above has its points at the position of the pins, for this a second one is needed that connects the mids of all polygon segments.
When implementing that, the resulting chain looked like this (I've drawn the polygon on top to illustrate):
Where the "straight" part of the chain morphs to the "curved" one, the chain links are seriously misaligned. The reason is that the marker orientation bisects the incoming and outgoing tangent at a vertex. Both for the straight section and while following the arc of the gear, that works out correctly, but where both meet, it obviously doesn't.
To get the orientation of the markers correct, the tangents at the points must match the direction of the original polygon. Robert proposed to draw an arc between the points. That probaböy would work, but it is complicated to compute the correct arc radii and positions.
I've come up with a method that produces a smooth path that looks a bit crooked, but the line will never show in the end, and its computation is really straightforward - I actually did that with an Excel sheet.
Lets say we have a list of points marking the pin positions:
a b c d e f g ...
There are two kinds of links (front and back), so mark every other middle of two points:
a b c d e f g ...
ab cd ef
a, ab, c are in a straight line, same as c, cd, d and so on. Now if you draw a path with the following command
<path d="M a L ab C b c cd d e ef f g ..." marker-mid="url(#link1)" />
point a will hold no marker, as its not in the middle. The next vertices, which position the markers, are ab, cd, ef, ..., while b, c, d, e, ... are control points of a cubic Bezier curve. What that means is: the path tangent in vertex ab is the straight line from a to b, in cd from c to d, and so on.
Here is a screenshot from Inkscape to illustrate:
The other links can be describes accordingly as
<path d="M b L bc C c d de e f fg g ..." marker-mid="url(#link2)" />
If one wants to get fancy, it is even possible to shorten that a bit with the S command that takes the control point before a vertex and implicitely adds its reflection as the next control point after (points e, g, ... are computed):
<path d="M b L bc C c d de S f fg h hj ..." marker-mid="url(#link2)" />
For the grande finale, here is the finished drawing. Just for the heck of it, I've drawn the gear teeths with the same marker technique.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 600" width="600" height="450">
<defs>
<marker id="blade1" markerUnits="userSpaceOnUse" orient="auto" overflow="visible">
<path style="fill:#ececf4" d="M 16.5,-1.556 13.947,-1.78 A 14,14 0 0 1 -13.947,-1.78 L -16.5,-1.556 0,200 Z" />
<path style="fill:none;stroke:#000" d="M 16.5,-1.556 13.947,-1.78 A 14,14 0 0 1 -13.947,-1.78 L -16.5,-1.556" />
</marker>
<marker id="blade2" markerUnits="userSpaceOnUse" orient="auto" overflow="visible">
<path style="fill:#ececf4" d="M 16.5,0.507 13.694,-0.089 A 14,14 0 0 1 -13.694,-0.089 L -16.5,0.507 0,90 Z" />
<path style="fill:none;stroke:#000" d="M 16.5,0.507 13.694,-0.089 A 14,14 0 0 1 -13.694,-0.089 L -16.5,0.507" />
</marker>
<path id="plate" d="M -9.689,-10.392 C -4.782 -7.56 4.782 -7.56 9.689,-10.392 A 12,12 0 1 1 9.689,10.392 C 4.782 7.56 -4.782 7.56 -9.689,10.392 A 12,12 0 1 1 -9.689,-10.392 Z" />
<marker id="link1" style="stroke:#000" markerUnits="userSpaceOnUse" orient="auto" overflow="visible">
<use xlink:href="#plate" style="fill:#ddd" />
<circle id="pin" style="fill:#888;stroke:#000" cx="15.689" cy="0" r="4" />
<use xlink:href="#pin" x="-31.378" />
</marker>
<marker id="link2" style="stroke:#000" markerUnits="userSpaceOnUse" orient="auto" overflow="visible">
<use xlink:href="#plate" style="fill:#bbb" />
</marker>
<g id="center1">
<circle r="80" cx="250" cy="300" />
<g id="cq">
<circle r="10" cx="345" cy="265.42" />
<path d="M 252.984,200.049 A 100,100 0 0 1 324.557,233.367 25,25 0 0 0 361.957,200.182 150,150 0 0 0 254.883,150.086 25,25 0 0 0 252.984,200.049 Z" />
</g>
<use xlink:href="#cq" transform="rotate(90 250,300)" />
<use xlink:href="#cq" transform="rotate(180 250,300)" />
<use xlink:href="#cq" transform="rotate(270 250,300)" />
</g>
<mask id="m1">
<rect fill="white" x="50" y="100" width="400" height="400" />
<use xlink:href="#center1" fill="black" />
</mask>
<path id="center2" d="M 647.75 277.2 L 642.04 279.05 L 643.04 282.13 A 20 20 0 0 0 633.31 292.96 L 630.13 292.28 L 628.88 298.15 L 632.06 298.83 A 20 20 0 0 0 636.55 312.68 L 634.38 315.08 L 638.84 319.1 L 641.01 316.69 A 20 20 0 0 0 655.25 319.73 L 656.25 322.8 L 661.96 320.95 L 660.96 317.87 A 20 20 0 0 0 670.7 307.04 L 673.87 307.72 L 675.12 301.85 L 671.94 301.17 A 20 20 0 0 0 667.45 287.32 L 669.62 284.92 L 665.16 280.9 L 662.99 283.31 A 20 20 0 0 0 648.75 280.27 L 647.75 277.2 z" />
<mask id="m2">
<rect fill="white" x="572" y="220" width="160" height="160" />
<use xlink:href="#center2" fill="black" />
</mask>
</defs>
<g mask="url(#m1)">
<path style="fill:none;marker-mid:url(#blade1)" d="M 430,300 427.27,331.26 419.14,361.56 405.88,390 387.89,415.7 365.7,437.89 340,455.88 311.56,469.14 281.26,477.27 250,480 218.74,477.27 188.44,469.14 160,455.88 134.3,437.89 112.11,415.7 94.12,390 80.86,361.56 72.73,331.26 70,300 72.73,268.74 80.86,238.44 94.12,210 112.11,184.3 134.3,162.11 160,144.12 188.44,130.86 218.74,122.73 250,120 281.26,122.73 311.56,130.86 340,144.12 365.7,162.11 387.89,184.3 405.88,210 419.14,238.44 427.27,268.74 430,300 427.27,331.26" />
</g>
<use xlink:href="#center1" style="fill:none;stroke:#000" />
<g mask="url(#m2)">
<path style="fill:none;marker-mid:url(#blade2)" d="M 578.19,315.69 V 284.31 L 590.95,255.65 614.27,234.65 644.11,224.95 675.32,228.23 702.49,243.92 720.94,269.31 727.46,300 720.94,330.69 702.49,356.08 675.32,371.77 644.11,375.05 614.27,365.35 590.95,344.35 578.19,315.69 V 284.31"/>
</g>
<use xlink:href="#center2" style="fill:none;stroke:#000" />
<path style="fill:none;marker-mid:url(#link2)" d="M 281.26 477.27 L 265.63 478.63 C 250 480 218.74 477.27 203.59 473.21 S 160 455.88 147.15 446.89 112.11 415.7 103.11 402.85 80.86 361.56 76.79 346.41 70 300 71.37 284.37 80.86 238.44 87.49 224.22 112.11 184.3 123.21 173.21 160 144.12 174.22 137.49 218.74 122.73 234.37 121.37 281.26 122.73 296.41 126.79 341.88 138.97 357.03 143.03 402.5 155.2 417.66 159.26 463.13 171.43 478.28 175.49 523.75 187.66 538.91 191.72 584.38 203.89 599.54 207.95 645.01 220.12 660.16 224.18 702.49 243.92 711.71 256.61 727.46 300 724.2 315.35 702.49 356.08 688.91 363.92 645.01 379.88 629.85 383.94 584.38 396.11 569.22 400.17 523.75 412.34 508.6 416.4 463.13 428.57 447.97 432.63 402.5 444.8 387.35 448.86 341.88 461.03 326.72 465.09 281.26 477.27 265.63 478.63 L 250 480" />
<path style="fill:none;marker-mid:url(#link1)" d="M 311.56 469.14 L 296.41 473.21 C 281.26 477.27 250 480 234.37 478.63 S 188.44 469.14 174.22 462.51 134.3 437.89 123.21 426.79 94.12 390 87.49 375.78 72.73 331.26 71.37 315.63 72.73 268.74 76.79 253.59 94.12 210 103.11 197.15 134.3 162.11 147.15 153.11 188.44 130.86 203.59 126.79 250 120 265.63 121.37 311.56 130.86 326.72 134.91 372.19 147.08 387.35 151.14 432.82 163.31 447.97 167.37 493.44 179.54 508.6 183.6 554.07 195.77 569.22 199.83 614.69 212 629.85 216.06 675.32 228.23 688.91 236.08 720.94 269.31 724.2 284.65 720.94 330.69 711.71 343.39 675.32 371.77 660.16 375.82 614.69 388 599.54 392.05 554.07 404.23 538.91 408.28 493.44 420.46 478.28 424.51 432.82 436.69 417.66 440.74 372.19 452.92 357.03 456.97 311.56 469.14 296.41 473.21 L 281.26 477.27" />
</svg>
In SVG, I define one chainlink and create as many copies as necessary (reference point being at the center of the chainlink) :
<use class="chain chainlink1" xlink:href="#chainlink" />
<use class="chain chainlink2" xlink:href="#chainlink" />
<use class="chain chainlink3" xlink:href="#chainlink" />
<use class="chain chainlink4" xlink:href="#chainlink" />
....
in the css, I define the path to follow
.chain {
offset-path: path("m 50,150 c -2,-19 12,-36 24,-44 13,-8 176,-54 226,-56
60,0 100,49.17776 100,100 0,51 -42,99 -100,99 C 262,250
95,206 72,191 58,182 50,166 50,150 Z");
animation: cycle 20s linear infinite;
}
#keyframes cycle {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
And the delay of each chainlink
.chainlink2 {
animation-delay: 1s;
}
.chainlink3 {
animation-delay: 2s;
}
...
Durations and delays depends on length of the chainlink, path... and desired speed
Here is one solution: you can make a <polyline> or <polygon> with <marker> elements as long as you're willing to do the math to ensure that the distance between the polygon points exactly match the axis-to-axis distance of your chain element (ie. it's not the total length of the element, but the repeated placement distance that counts). It requires the use of a^2 + b^2 = c^2 where c is this pitch distance (a constant), and a and b are the horizontal and vertical distances from the preceding point, respectively. In other words, you need to calculate the x, y coordinates of the chain axis centers (*) (if you do that, then you might as well place them one by one, but then you'll have a lot of DOM elements).
You'll need orient="auto" to align the chain elements with the polygon, and also use the properties viewBox,refX, refY, markerWidth, markerHeight so that one of the axes of the chain element aligns perfectly with the polygon point (and then, via using the above Pythagoras theorem, the other chain element center will correspond to the next polygon point).
Here are a few examples for the <marker>:
https://codepen.io/monfera/pen/ppaRNK
https://codepen.io/monfera/pen/xXmpbY
https://codepen.io/monfera/pen/oLoRgX
As I look at your chain (normal bicycle chain), it's best to make two almost identical polygons atop of one another, with a half-pitch offset between the two:
in the background, the partially occluded chain elements
in the foreground, the 8-shaped, fully visible link plates
As a result, your chain will be seamless, as long as your points are at the right place. You can even set up a loop of transitioning between adjacent elements for an animated chain :-) (it's enough to transition with the chain pitch only, because a chain pitch translation will get you back to an indistinguishable view)
(*) As ccprog points out below, to make the individual chain elements better aligned, it's not an axis center but the chain center that must sit on the polygon. This might make the math a bit more tedious if there are sharp turns, but in this specific case (bicycle chain) the circle radii seem sufficiently large to not lead to a noticeable separation of the chain elements.