I'm trying to create a custom progress bar for the HTML5 video tag. The progress bar doesn't need any controls, it's just an SVG that I'd like to fill as the video progresses.
The video is a React component:
import React, {useRef} from 'react'
const VideoSection = () => {
const videoRef = useRef()
const handlePlayVideo = () => videoRef.current.play()
return (
<div>
<button onClick={handlePlayVideo}>
Play video
</button>
<video src='./video.mp4' ref={videoRef}/>
//The SVG I'd like to animate
<svg width="595" height="28" viewBox="0 0 595 28" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M0 14C0 6.26801 6.26801 0 14 0H595V28H14C6.26801 28 0 21.732 0
14V14Z" fill="#072760"/>
</svg>
</div>
)
}
export default VideoSection
Or is there an alternative way to approach/build the progress bar? Thanks!
I'm working on a project with react native and we chose to use react-native-svg in order to display our map. Everything's worked perfectly so far, except for this :
I have an array of objects that each have coordinates and a size. I want to loop through this array to display an image at all of these coordinates. But no luck, I managed to diplay shapes and texts but no images.
Here is the render function of my parent element :
render() {
return (
<View>
<Svg
height={screen.height}
width={screen.width}
>
<G
width={mapSize.x}
height={mapSize.y}
x={this.state.center.x}
y={this.state.center.y}
originX={mapSize.x / 2}
originY={mapSize.y / 2}
rotation={this.state.orientation}
scale={1}
>
<Rect
width={mapSize.x}
height={mapSize.y}
x={0}
y={0}
scale={1}
fill="#0071e9"
/>
<G
width={mapSize.x}
height={mapSize.y}
x={this.state.cnv.x}
y={this.state.cnv.y}
scale={1}
>
// this is my child component's tag
<Islands
islands={this.state.contentToRender}
deg={this.state.orientation}
/>
</G>
</G>
<Image
x={(screen.width / 2) - 50}
y={-(screen.height / 2) + 94.46}
width="100"
height="189.9"
preserveAspectRatio="xMidYMid slice"
href={images.bateau}
/>
</G>
</Svg>
</View>
)
}
}
here is my child component's render :
render(){
const that = this
return(
this.props.islands.map((c) => {
return (
<Image
key={ c.id }
x={ c.position.x }
y={ c.position.y }
width={ c.size.x }
height={ c.size.y }
preserveAspectRatio="xMidYMid slice"
rotation={-that.props.deg}
originX={ c.position.x + (c.size.x / 2) }
originY={ c.position.y + (c.size.y / 2) }
href={images.rouge}
/>
)
})
)
}
And finally my images.js file :
const images = {
boussole: require('./img/Boussole.png'),
aiguille: require('./img/Aiguille.png'),
bateau: require('./img/Bateau.png'),
home: require('./img/home.png'),
cyclop_island: require('./img/ile_cyclope_navig.png'),
rouge: require('./img/smiley-carre-rouge-simple-face-magnetique.jpg'),
}
export default images
I have no issue with any aspect of the Svg component, I can display every image of the images.js file as long as they are not in a loop.
Also, even if the image doesn't display the console shows no error.
I'm learning CSS and working on a problem involving dynamic color selection of a path.
I've a SVG which can have any number of paths (1+) and there are pre-defined colors which should be assigned to paths depending on their order. (A web service returns a collection of paths).
For simplicity, Let's say I've 4 known paths and there are two colors possible: Green and deepPink.
Question: How can I assign following colors dynamically (without creating individual classes for each path):
Path1: Green
Path2: deepPink
Path3: Green
Path4: deepPink
.path_group {
stroke="black";
fill="none";
stroke-width="2";
}
.myPath{
stroke:deepPink;
stroke-width:1.3;
}
/*
Color1: Green
Color2: deepPink
*/
<svg width="100%" height="100%" viewBox="0 0 260 200"
xmlns="http://www.w3.org/2000/svg">
<g class="path_group" >
<path class="myPath" d="M10,25 L110,10" />
<path class="myPath" d="M10,35 L110,20" />
<path class="myPath" d="M10,45 L110,30" />
<path class="myPath" d="M10,55 L110,40" />
</g>
</svg>
I'm more interested in doing it in natively using CSS/JS instead of adding any framework or library to my project. Suggestion regarding any particular concept is appreciated.
Demo: Codepen
Current approach
this.colors = ["green", "deepPink"];
this.getColor = function (pathIndex) {
return this.colors[pathIndex % this.colors.length];
};
You can use CSS3 :nth-child() selector (w3schools) as this:
.myPath { stroke-width:1.3; }
.path_group .myPath:nth-child(4n+1)
{ stroke:red; }
.path_group .myPath:nth-child(4n+2)
{ stroke:blue; }
.path_group .myPath:nth-child(4n+3)
{ stroke:green; }
.path_group .myPath:nth-child(4n+4)
{ stroke:deepPink; }
See updated codepen here.
I'm having a problem with the SVG checkintersection() function. All I want to do is to check whether a small SVG-rectangle intersects the area of an SVG-path, but I can't figure out what to call the function on (I already tried to call it on the SVG DOM object, among several other things google turned up).
So what I need to know is what to put in for the placeholder ("foo") in this snippet:
var closedPath = document.getElementById(closedPath);
var rectangle = document.getElementById(rectangle);
if (foo.checkIntersection(closedPath, rectangle)) {
//do stuff
};
with the HTML being something along the lines of
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svgroot">
<g>
<path id="closedPath" fill="{$c5}" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/>
<rect id="rectangle" fill="white" x="-126828" y="0" width="45000" height="45000"/>
</g>
</svg>
</body>
</html>
Any help would be much appreciated!
Edit: Just wanted to add that I now use a workaround, which consists of converting the SVG path to an array of point coordinates using a parser function I wrote, which is then put into a simple coordinate-test function.
Also this may have been a solution Hit-testing SVG shapes?
checkIntersection is a method on the <svg> element so you'd want something like this...
var svg = document.getElementById("svgroot");
var closedPath = document.getElementById(closedPath);
var rectangle = document.getElementById(rectangle);
var rect = svg.createSVGRect();
rect.x = rectangle.animVal.x;
rect.y = rectangle.animVal.y;
rect.height = rectangle.animVal.height;
rect.width = rectangle.animVal.width;
svg.checkIntersection(closedPath, rect) {
// do stuff
}
Note also how the second argument has to be an SVGRect and not an element.
SVG elements support SMIL animation, you could equally well write rectangle.baseVal.x etc but that wouldn't necessarily reflect the rectangle's current position if you were animating the rectangle. If you're not using SMIL then rectangle.baseVal.x = rectangle.animVal.x
Because a <rect> can have things like rounded corners it doesn't have an SVGRect interface so you have to convert from the interface it does have (SVGRectElement) to the one you need (SVGRect)
<svg width="390" height="248" viewBox="-266600, -68800, 195000, 124000" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path id="closedPath" fill="#ff9966" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/>
<rect id="rectangle" fill="#66ff66" x="-126828" y="0" width="45000" height="45000"/>
</svg>
<script>
var rectangle = document.getElementById('rectangle');
var closedPath = document.getElementById('closedPath');
var svgRoot = closedPath.farthestViewportElement;
var rect = svgRoot.createSVGRect();
rect.x = rectangle.x.animVal.value;
rect.y = rectangle.y.animVal.value;
rect.height = rectangle.height.animVal.value;
rect.width = rectangle.width.animVal.value;
var hasIntersection = svgRoot.checkIntersection(closedPath, rect);
console.log(hasIntersection);
</script>
I'm developing a map, in Javascript using SVG to draw the lines.
I would like to add a feature where you can search for a road, and if the road is found, a circle appears on the map.
I know i can draw a circle in SVG, but my problem is that, the size of the circle should not change depending on the zoom-level. In other words the circle must have the same size at all times.
The roads on my map have this feature, all i had to do was add
vector-effect="non-scaling-stroke"
to the line attributes..
A line looks like this.
<line vector-effect="non-scaling-stroke" stroke-width="3" id = 'line1' x1 = '0' y1 = '0' x2 = '0' y2 = '0' style = 'stroke:rgb(255,215,0);'/>
The circle looks like this.
<circle id = "pointCircle" cx="0" cy="0" r="10" stroke="red" stroke-width="1" fill = "red"/>
Is it possible to define the circle as "non-scaling" somehow?
It took me a while, but I finally got the math clean. This solution requires three things:
Include this script in your page (along with the SVGPan.js script), e.g.
<script xlink:href="SVGPanUnscale.js"></script>
Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g.
unscaleEach("g.non-scaling > *, circle.non-scaling");
Use transform="translate(…,…)" to place each element on the diagram, not cx="…" cy="…".
With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements.
Demo: http://phrogz.net/svg/scale-independent-elements.svg
Library
// Copyright 2012 © Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/JS/_ReuseLicense.txt
// Undo the scaling to selected elements inside an SVGPan viewport
function unscaleEach(selector){
if (!selector) selector = "g.non-scaling > *";
window.addEventListener('mousewheel', unzoom, false);
window.addEventListener('DOMMouseScroll', unzoom, false);
function unzoom(evt){
// getRoot is a global function exposed by SVGPan
var r = getRoot(evt.target.ownerDocument);
[].forEach.call(r.querySelectorAll(selector), unscale);
}
}
// Counteract all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
function unscale(el){
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf){
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
xf = el.scaleIndependentXForm = svg.createSVGTransform();
el.transform.baseVal.appendItem(xf);
}
var m = svg.getTransformToElement(el.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
}
Demo Code
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Scale-Independent Elements</title>
<style>
polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; }
circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 }
</style>
<g id="viewport" transform="translate(500,300)">
<polyline points="-100,-50 50,75 100,50" />
<g class="non-scaling">
<circle transform="translate(-100,-50)" r="10" />
<polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" />
</g>
<circle class="non-scaling" transform="translate(50,75)" r="10" />
</g>
<script xlink:href="SVGPan.js"></script>
<script xlink:href="SVGPanUnscale.js"></script>
<script>
unscaleEach("g.non-scaling > *, circle.non-scaling");
</script>
</svg>
If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width.
In other words, you could wrap the circles in a <marker> element and then use those markers where you need them.
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
<marker id="Triangle"
viewBox="0 0 10 10" refX="0" refY="5"
markerUnits="strokeWidth"
markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<path d="M 100 100 l 200 0" vector-effect="non-scaling-stroke"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
<path d="M 100 200 l 200 0"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
</svg>
The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).
Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that
transform.baseVal.appendItem
This seems to work better. Even works in IE 10.
EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though.
translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100)
function unscale()
{
var xf = this.ownerSVGElement.createSVGTransform();
var m = this.ownerSVGElement.getTransformToElement(this.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
var SVG_TRANSFORM_MATRIX = 1;
var SVG_TRANSFORM_TRANSLATE = 2;
var baseVal = this.transform.baseVal;
if(baseVal.numberOfItems == 0)
baseVal.appendItem(xf);
else
{
for(var i = 0; i < baseVal.numberOfItems; ++i)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1)
{
baseVal.appendItem(xf);
}
if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX)
baseVal.replaceItem(xf, i);
else
baseVal.insertItemBefore(xf, i);
break;
}
}
}
}
EDIT2:
Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually:
var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());
It's discussed here and here
It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed