I have a JavaScript file which essentially draws a line when the user scrolls down the page. It draws a line for the first SVG but not for the other (which uses the same class). For example, I have the following code block in both text-repeater.php and text-block.php:
<svg height="100" width="200">
<line class="line__svg" id="line" x1="0" y1="0" x2="0" y2="200" />
</svg>
And here is my parallax.js file:
var scrollLine = document.getElementById("line");
var length = scrollLine.getTotalLength();
// Start position of drawing
scrollLine.style.strokeDasharray = length;
// Find scroll percentage on scroll
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse drawing (when scrolling upwards)
scrollLine.style.strokeDashoffset = length - draw;
}
The line draws in text-block.php but not in text-repeater.php
Ideas why?
Also, on a related note. How can I get the line to draw longer? It's very subtle at the moment.
That's how IDs work. You only can have one instance of an Id (kind of - there are tricks but you don't want to do that).
Instead use classes...
<tagName class="line">
...and loop over it:
// Selecting all lines
const scrollLineCollection = document.querySelectorAll(".line");
for ( const scrollLine of scrollLineCollection ) {
// do something with scrollLine
}
Related
I'd like to move an SVG element (image) slowly from its current position (wherever it is) to a given point when an event is handled.
Since I don't know what the position is, I cannot prepare CSS animation based on keyframes.
So I tried doing the task via DOM manipulating:
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
image.appendChild(animation);
If I set duration to 3s or 5s, it starts moving as soon as the animate element is appended, but when the animation is over the image is returned to the original position. (As I understand this is by design, but I'd like the image to keep its new position).
The second problem is when I set duration to 2s or less, the image stops moving smoothly, it just flickers to the left border and back (I don't know why).
Maybe, there is a better approach?
UPDATE
As it's answered below, the image keeps its position if fill is set to freeze. To reproduce the second problem it is enough to set timeout:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" class="inline-block mb-1">
<image href="https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/SVG/Element/image/mdn_logo_only_color.png"
height="200" width="200" x="200" y="200"/>
<script>
// <![CDATA[
var startAnimation = function ()
{
const svgNS = "http://www.w3.org/2000/svg";
var image = document.querySelector('image');
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
animation.setAttribute('fill', 'freeze');
image.appendChild(animation);
};
document.addEventListener('DOMContentLoaded', () => window.setTimeout(startAnimation, 1000));
// ]]>
</script>
</svg>
The image initially twitches and starts moving smoothly only after a half of the way. Chrome 102, x64. As for my computer performance, an animation based on window.requestAnimationFrame works with no glitches.
Set the 'fill' attribute of your animation:
animation.setAttribute('fill', 'freeze');
See here (MDN docs) for more details.
As for the jumpy animation behaviour, it hasn't happened in my tests, neither in the live example below nor on a standalone file.
Live example
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" class="inline-block mb-1">
<image href="https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/SVG/Element/image/mdn_logo_only_color.png" height="200" width="200" x="200" y="200"/>
<script>
// <![CDATA[
document.addEventListener ( 'DOMContentLoaded', function () {
const svgNS = "http://www.w3.org/2000/svg";
var image = document.querySelector('image');
var curX = parseFloat(image.getAttribute('x'));
var curY = parseFloat(image.getAttribute('y')); // Not used yet.
var curOpacity = parseFloat(image.getAttribute('opacity')); // Not used yet.
var animation = document.createElementNS(svgNS, 'animate');
animation.setAttribute('attributeName', 'x');
animation.setAttribute('from', curX);
animation.setAttribute('to', 0); // Move the image to the left border.
animation.setAttribute('dur', '2s'); // '3s', '5s'
animation.setAttribute('repeatCount', 1);
animation.setAttribute('fill', 'freeze');
image.appendChild(animation);
});
// ]]>
</script>
</svg>
UPDATE
The OP's bumpy animation problem when starting the animation through a timeout callback can be remedied by setting the animation element's begin attribute to the timeout delay:
animation.setAttribute('begin', '1s');
I've got a small application written with Raphael.js that paints a node network with SVG and rearranges it based on selections given.
What I need to be able to do is capture the svg picture I've drawn and display it on a "mini-map" type of display on the bottom of the screen. Since I have no idea what the canvas will look like when I capture it, I want to know if it's possible to just capture it as a bitmap or something similar?
Put together a quick fiddle to show close to what I mean:
http://jsfiddle.net/WNnCH/1/
Is there some way to do something like: SVG.copy? or SVG.img? (I'm sure there's not, but you know what I mean?)
I've looked in to how I could just capture the whole SVG and resize it (which is more code than I think I'd like to write or is necessary), or even copy an entire SVG, but I don't know if it's possible to capture an svg as an image and scale it down to what I need.
The minimap doesn't need to be a SVG, it can just be an image, because it's basically just a history of what the screen looked like on a prior action.
Thanks for all suggestions!
You can easily make thumbnails of a primary SVG by creating a different scaled-down SVG that points the first one.
<div id="main">
<svg id="mainsvg" viewBox="0 0 1000 1000">
<rect x="100" y="100" width="500" height="500" fill="green"
transform="rotate(10,350,350)"/>
<rect x="400" y="400" width="500" height="500" fill="orange"
transform="rotate(-10,650,650)"/>
</svg>
</div>
<div id="thumb">
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#mainsvg" />
</svg>
</div>
The SVG sizes are determined by CSS:
#main {
width: 400px;
height: 400px;
}
#thumb {
width: 80px;
height: 80px;
}
You can do anything you want to the main SVG and the thumb will reflect all the changes.
Demo here
There is a way to do this using just Raphael, creating a set with your minimap elements and then cloning it. However it's more tricky since you want the minimap to be a separate entity/paper. I borrowed some code from this answer to clone your map to a new paper and it works well (jsfiddle)
(function (R) {
var cloneSet; // to cache set cloning function for optimisation
/**
* Clones Raphael element from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelElement
*/
R.el.cloneToPaper = function (targetPaper) {
return (!this.removed &&
targetPaper[this.type]().attr(this.attr()));
};
/**
* Clones Raphael Set from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelSet
*/
R.st.cloneToPaper = function (targetPaper) {
targetPaper.setStart();
this.forEach(cloneSet || (cloneSet = function (el) {
el.cloneToPaper(targetPaper);
}));
return targetPaper.setFinish();
};
}(Raphael));
var paper = Raphael(30, 30, 200, 200);
var circle = paper.circle(30, 30, 30);
circle.attr("fill", "#ccc");
var circle1 = paper.circle(70, 70, 30);
circle1.attr("fill", "#ccc");
var circle2 = paper.circle(110, 30, 30);
circle2.attr("fill", "#ccc");
var circle3 = paper.circle(30, 110, 30);
circle3.attr("fill", "#ccc");
var circle4 = paper.circle(110, 110, 30);
circle4.attr("fill", "#ccc");
var s = paper.set();
s.push(circle, circle1, circle2, circle3, circle4);
var minipaper = Raphael(document.getElementById("minimap").getElementsByTagName("div")[1], 140, 140);
var miniset = s.cloneToPaper(minipaper);
miniset.transform("s0.5,0.5,55,55"); // scale by 0.5 around (55,55)
I am currently working on an aplication that supports drag&drop using this code for the drag&drop. the issue is that i need to move connected lines accordingly, and i can't figure out how to get the X and Y coordinates to change either the x1/y1 pair or the x2/y2 pair to update the line.
I have already tried asking the element for its position, using its bounding box, and the numbers do not match the actual position after the drag&drop.
Any ideas how can i achieve that? (Given the information calculated in the code, linked above)
Rather than BBox of a transformed element, you can insert the element into an SVG "Wrapper", then get the bbox of that.
I have reworked your example and added to an HTML5 document. I included an svg wrapper element, and three lines attached the the center of each circle. When it its dragged the lines follow.
This is shown below. (Note: Chrome Hangs on text element)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Drag And Drop</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Drag And Drop</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
A nice little demo of drag-and-drop functionality in SVG,
written by Doug Schepers on February 16, 2004.
Use or misuse this code however you wish.</div>
<table><tr>
<td>
<div id="svgDiv" style='background-color:lightgreen;width:600px;height:300px;'>
<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' id="mySVG"
onload='Init(evt)'
onmousedown='Grab(evt)'
onmousemove='Drag(evt)'
onmouseup='Drop(evt)'>
<rect id='BackDrop' x='-10%' y='-10%' width='110%' height='110%' fill='none' pointer-events='all' />
<circle id='BlueCircle' cx='25' cy='25' r='20' style='fill:blue; '/>
<circle id='RedCircle' cx='125' cy='25' r='20' style='fill:red; '/>
<circle id='OrangeCircle' cx='225' cy='25' r='20' style='fill:orange; '/>
<text id='DraggableText' x='20' y='200' style='fill:red; font-size:18px; font-weight:bold;'>Draggable Text</text>
<rect id='GreenRectangle' x='50' y='70' width='100' height='100' style='fill:green; '/>
<g id='Folder'>
<rect id='FolderRectangle' x='300' y='100' width='200' height='150' style='fill:tan; stroke:brown; stroke-width:3;'/>
</g>
<svg id="Wrapper" />
<line id=blueLine stroke=blue stroke=3 x1=25 y1=25 x2=25 y2=25 />
<line id=redLine stroke=red stroke=3 x1=125 y1=25 x2=125 y2=25 />
<line id=orangeLine stroke=orange stroke=3 x1=225 y1=25 x2=225 y2=25 />
</svg>
</div>
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:5px;position:absolute;top:5px;left:5px;background-color:gainsboro;'>OK in:IE11/FF23...*CH hangs<br /></div>
<script id=myScript>
//var SVGDocument = null;
//var SVGRoot = null;
var TrueCoords = null;
var GrabPoint = null;
var BackDrop = null;
var DragTarget = null;
var DragLine = null;
function Init(evt)
{
//SVGDocument = evt.target.ownerDocument;
//SVGRoot = SVGDocument.documentElement;
// these svg points hold x and y values...
// very handy, but they do not display on the screen (just so you know)
TrueCoords = mySVG.createSVGPoint();
GrabPoint = mySVG.createSVGPoint();
// this will serve as the canvas over which items are dragged.
// having the drag events occur on the mousemove over a backdrop
// (instead of the dragged element) prevents the dragged element
// from being inadvertantly dropped when the mouse is moved rapidly
// BackDrop = SVGDocument.getElementById('BackDrop');
}
function Grab(evt)
{
// find out which element we moused down on
var targetElement = evt.target;
// you cannot drag the background itself, so ignore any attempts to mouse down on it
if ( BackDrop != targetElement )
{
//set the item moused down on as the element to be dragged
DragTarget = targetElement;
Wrapper.appendChild(DragTarget)
if(DragTarget.id=="BlueCircle")
DragLine=blueLine
if(DragTarget.id=="RedCircle")
DragLine=redLine
if(DragTarget.id=="OrangeCircle")
DragLine=orangeLine
// move this element to the "top" of the display, so it is (almost)
// always over other elements (exception: in this case, elements that are
// "in the folder" (children of the folder group) with only maintain
// hierarchy within that group
DragTarget.parentNode.appendChild( DragTarget );
// turn off all pointer events to the dragged element, this does 2 things:
// 1) allows us to drag text elements without selecting the text
// 2) allows us to find out where the dragged element is dropped (see Drop)
DragTarget.setAttributeNS(null, 'pointer-events', 'none');
// we need to find the current position and translation of the grabbed element,
// so that we only apply the differential between the current location
// and the new location
var transMatrix = DragTarget.getCTM();
GrabPoint.x = TrueCoords.x - Number(transMatrix.e);
GrabPoint.y = TrueCoords.y - Number(transMatrix.f);
}
};
function Drag(evt)
{
// account for zooming and panning
GetTrueCoords(evt);
// if we don't currently have an element in tow, don't do anything
if (DragTarget)
{
// account for the offset between the element's origin and the
// exact place we grabbed it... this way, the drag will look more natural
var newX = TrueCoords.x - GrabPoint.x;
var newY = TrueCoords.y - GrabPoint.y;
BB=Wrapper.getBBox()
var bbx=BB.x
var bby=BB.y
var bbw=BB.width
var bbh=BB.height
Cx=bbx+.5*bbw
Cy=bby+.5*bbh
DragLine.x2.baseVal.value=Cx
DragLine.y2.baseVal.value=Cy
// apply a new tranform translation to the dragged element, to display
// it in its new location
DragTarget.setAttributeNS(null, 'transform', 'translate(' + newX + ',' + newY + ')');
}
};
function Drop(evt)
{
// if we aren't currently dragging an element, don't do anything
if ( DragTarget )
{ mySVG.appendChild(DragTarget) //--remove from Wrapper---
// since the element currently being dragged has its pointer-events turned off,
// we are afforded the opportunity to find out the element it's being dropped on
var targetElement = evt.target;
// turn the pointer-events back on, so we can grab this item later
DragTarget.setAttributeNS(null, 'pointer-events', 'all');
if ( 'Folder' == targetElement.parentNode.id )
{
// if the dragged element is dropped on an element that is a child
// of the folder group, it is inserted as a child of that group
targetElement.parentNode.appendChild( DragTarget );
//alert(DragTarget.id + ' has been dropped into a folder, and has been inserted as a child of the containing group.');
}
else
{
// for this example, you cannot drag an item out of the folder once it's in there;
// however, you could just as easily do so here
//alert(DragTarget.id + ' has been dropped on top of ' + targetElement.id);
}
// set the global variable to null, so nothing will be dragged until we
// grab the next element
DragTarget = null;
}
svgSourceValue.value=svgDiv.innerHTML
};
function GetTrueCoords(evt)
{
// find the current zoom level and pan setting, and adjust the reported
// mouse position accordingly
var newScale = mySVG.currentScale;
var translation = mySVG.currentTranslate;
TrueCoords.x = (evt.clientX - translation.x)/newScale;
TrueCoords.y = (evt.clientY - translation.y)/newScale;
};
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
svgSourceValue.value=svgDiv.innerHTML
jsValue.value=myScript.text
}
</script>
</body>
</html>
Here is what I want to do :
var a = Snap("#id");
var group = new SnapGroup(); // unfortunatly didnt find how to do it
// for some reasons i dont want to do a.group();
group.circle(5,5,5);
a.add(group);
Here is what I did :
var a = Snap("#id");
s = Snap(); // creates a SVG element instead of a group
s.circle(5,5,5);
a.add(s);
It works, the circle is rendered, but then I cannot move the group :
s.attr({"x":60}); // the group doesnt move
Actually it looks like that when we embed and <svg> elements into an other one. Then it becomes impossible to move the embeded svg element in the parent one.
I would like to know how to create a group element without doing snapInstance.group() ? And then add it to a Snap instance.
I'm still not quite sure from your description what you are after, as I suspect it may depend how you are generating the original group (if its just a bit of svg markup or imported).
Snap.parse('<g></g>'); may be enough to fiddle with, to parse into a fragment.
See if this helps...its an example with two separate SVG elements and Snap instances. It will draw a rect from the original SVG markup string with a group,add a circle from Snap, and on the 2nd instance it will translate the group by 75 as well.
<svg id="svg1" width="200" height="200"></svg><br />
<svg id="svg2" width="200" height="200"></svg>
...
var paper1 = Snap("#svg1");
var paper2 = Snap("#svg2");
var groupMarkup = '<g><rect x="0" y="0" width="70" height="70" opacity="0.3"/><text x="0" y="15">original</text></g>';
var parsedMarkup1 = Snap.parse( groupMarkup ); //parse from a string derived elsewhere
var parsedMarkup2 = Snap.parse( groupMarkup );
// example1 just use the markup with its original group
paper1.add( parsedMarkup1 )
.add( paper1.circle(100,50,50)
.attr('fill', 'red' ) );
// example2, will create a new group and add the existing group to it, and then move it
var outerG = paper2.g()
.transform('t75,0'); //create a group and move it
outerG.add( parsedMarkup2 ); //add the original group/square
outerG.add( paper2.circle(70,50,50) //add a circle
.attr('fill', 'blue' ) );
var clone = outerG.clone(); //lets create a clone
clone.transform('r45t50,50'); //translate and rotate the clone
fiddle here http://jsfiddle.net/v4bJa/
I want to drag path on SVG canvas.
The requirement is not to use any JS library.
Any example will help me.
I know with raphael.js its very easy.
Thanks.
Here is an example in pure JS:
<svg id='mysvg' height="800" width="800">
<path id='mypath' fill="#4444ff" stroke="#000000" d="M100,300L400,300L100,0Z" transform=""></path>
</svg>
JS:
el=document.getElementById("mypath");
sv=document.getElementById("mysvg");
flag=false; //to check if the mouse is currently down?
sv.onmousedown=function(e){
flag=true;
x1=e.clientX;
y1=e.clientY;
var t=el.getAttribute('transform');
if(t){
var parts = /translate\(\s*([^\s,)]+)[ ,]([^\s,)]+)/.exec(t);
var firstX = parts[1], firstY = parts[2];
x1=x1-firstX*1;
y1=y1-firstY*1;
}
/* x1 and y1 now contain the previous position*/
};
sv.onmousemove=function(e){
if(flag){
x=e.clientX;
y=e.clientY;
t="translate("+(x-x1)+","+(y-y1)+")"
el.setAttribute('transform',t);
}
};
sv.onmouseup=function(){flag=false};
fiddle: http://jsfiddle.net/gF8Wd/2/