Converting an SVG into a jQuery function - javascript

I have an svg
<svg class="svg_el" viewbox="0 0 100 100" preserveaspectratio="none">
<path class="overlay_path">
<animate attributeName="d" values="M 0 0 V 0 C 50 0 50 0 100 0 V 0 H 0; M 0 25 V 25 C 50 15 50 60 100 50 V 0 H 0; M 0 50 V 50 C 50 50 50 85 100 80 V 0 H 0; M 0 100 V 100 C 50 100 50 100 100 100 V 0 H 0" dur="0.4s" fill="freeze" repeatCount="1"></animate>
<animate attributeName="d" values="M 0 0 C 50 0 50 0 100 0 V 100 H 0; M 0 25 C 50 15 50 60 100 50 V 100 H 0; M 0 50 C 50 50 50 85 100 80 V 100 H 0; M 0 100 C 50 100 50 100 100 100 V 100 H 0" dur="0.4s" begin="0.73s" fill="freeze" repeatCount="1"></animate>
</path>
<path class="overlay_path">
<animate attributeName="d" values="M 0 0 V 0 C 50 0 50 0 100 0 V 0 H 0; M 0 25 V 25 C 50 15 50 60 100 50 V 0 H 0; M 0 50 V 50 C 50 50 50 85 100 80 V 0 H 0; M 0 100 V 100 C 50 100 50 100 100 100 V 0 H 0" dur="0.4s" begin="0.1s" fill="freeze" repeatCount="1"></animate>
<animate attributeName="d" values="M 0 0 C 50 0 50 0 100 0 V 100 H 0; M 0 25 C 50 15 50 60 100 50 V 100 H 0; M 0 50 C 50 50 50 85 100 80 V 100 H 0; M 0 100 C 50 100 50 100 100 100 V 100 H 0" dur="0.4s" begin="0.63s" fill="freeze" repeatCount="1"></animate>
</path>
<path class="overlay_path">
<animate attributeName="d" values="M 0 0 V 0 C 50 0 50 0 100 0 V 0 H 0; M 0 25 V 25 C 50 15 50 60 100 50 V 0 H 0; M 0 50 V 50 C 50 50 50 85 100 80 V 0 H 0; M 0 100 V 100 C 50 100 50 100 100 100 V 0 H 0" dur="0.4s" begin="0.2s" fill="freeze" repeatCount="1"></animate>
<animate attributeName="d" values="M 0 0 C 50 0 50 0 100 0 V 100 H 0; M 0 25 C 50 15 50 60 100 50 V 100 H 0; M 0 50 C 50 50 50 85 100 80 V 100 H 0; M 0 100 C 50 100 50 100 100 100 V 100 H 0" dur="0.4s" begin="0.53s" fill="freeze" repeatCount="1"></animate>
</path>
</svg>
I'm trying to use it as a page transition so that it covers the entire screen before the next page loads but the problem I'm having is that even with SVGSVGELEMENT.getCurrentTime(), I can't seem to pause it at the correct spot and so the svg will pause at different points.
$('.the_box').removeClass('loaded');
$('.ccs').load('/wordpress/wp-content/themes/Tsunami-Waves-PHP/img/waves.svg', function() {
var svgDoc = $('.ccs svg');
var animWatch = setInterval(function() {
if (svgDoc[0].getCurrentTime() > 0.56 && !($('.the_box').hasClass('loaded'))) {
svgDoc[0].pauseAnimations();
console.log(svgDoc[0].getCurrentTime());
} else if (svgDoc[0].getCurrentTime() > 0.56 && $('.the_box').hasClass('loaded')) {
svgDoc[0].unpauseAnimations();
$('.the_box').siblings('.slider-transition').html($('.the_box').html());
$('.slider-transition').children('.slider-transition').unwrap();
$('video').trigger('play');
clearInterval(animWatch);
}
}, 10);
});
// $('#holder').load(function(){ var imgcount = $('#holder img').length; $('#holder img').load(function(){ imgcount--; if (imgcount == 0) { /* now they're all loaded, let's display them! */ } }); });
$('.the_box').load(href + ' .slider-transition', function() {
var svgDoc = $('.ccs svg');
$(this).addClass('loaded');
$('.woocommerce-product-gallery').each(function() {
$(this).wc_product_gallery();
});
slideShowInit();
initParalax();
});
Even with the interval being 10 (or even 1), the pause will occur at completely different times and can't seem to catch it at the correct moment so I figure my best bet is to convert the svg into jQuery so that I have better control of it, is there an easy way of doing this or do I have to learn up on how to do it?

If I understand correctly what you want to achieve, the first animation for each path should run immediately, and the second only when the new page has loaded. If that is the case, you can do this explicitly.
Set an id="reveal" and begin="indefinite" for the earliest of the second animations, and start it with $('#reveal')[0].beginElementAt(). The other two animations can then be started with relative begin times: begin="reveal.begin+0.1s".
<svg class="svg_el" viewbox="0 0 100 100" preserveaspectratio="none">
<path class="overlay_path">
<animate attributeName="d" values="..." dur="0.4s" fill="freeze"></animate>
<animate attributeName="d" values="...." dur="0.4s" begin="reveal.begin+0.2s" fill="freeze"></animate>
</path>
<path class="overlay_path">
<animate attributeName="d" values="..." dur="0.4s" begin="0.1s" fill="freeze"></animate>
<animate attributeName="d" values="" dur="0.4s" begin="reveal.begin+0.1s" fill="freeze"></animate>
</path>
<path class="overlay_path">
<animate attributeName="d" values="" dur="0.4s" begin="0.2s" fill="freeze"></animate>
<animate id="reveal" attributeName="d" values="..." dur="0.4s" begin="indefinite" fill="freeze"></animate>
</path>
</svg>
For the start time of the second group of animations you now need to wait for the load event. If the first group of animations is still running, you can delay the start time for the second. The beginEvent triggers the other actions.
$('.the_box').removeClass('loaded');
var svgLoad = $.Deferred(), sliderLoad = $.Deferred();
// first animations start immediatly after svg load
$('.ccs').load('/wordpress/wp-content/themes/Tsunami-Waves-PHP/img/waves.svg', svgLoad.resolve);
$('.the_box').load(href + ' .slider-transition', sliderLoad.resolve);
// wait for both load events
$.when(svgLoad, sliderLoad).then(function() {
var svgDoc = $('.ccs svg');
// delay start time of second animations if load is earlier than 0.53s
var startTime = Math.max(0.53, svgDoc[0].getCurrentTime());
var reveal = $('#reveal');
// link DOM change and video play to animation beginEvent
reveal.on('beginEvent', function () {
$('.the_box').siblings('.slider-transition').html($('.the_box').html());
$('.slider-transition').children('.slider-transition').unwrap();
$('video').trigger('play');
});
reveal[0].beginElementAt(startTime);
$(this).addClass('loaded');
$('.woocommerce-product-gallery').each(function() {
$(this).wc_product_gallery();
});
slideShowInit();
initParalax();
});

Related

stack/add/composite/accumulate multiple transform=translate animations in the Web Animation API

I have been running around in circles
I use the Web Animation API transform:translate() to make my player run around the bases.
runto("first").then(() => runto("second")); does the correct animation,
but then deletes the first/previous translate to first base,
thus my player ends up in the wrong location
I have tried every combination of
https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/composite
https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/iterationComposite
Is it at all possible to preserve all the translates on one element?
JSFiddle playground: https://jsfiddle.net/WebComponents/8df2qsop/
let field = document.getElementById("FIELD");
let player = field.getElementById("player");
function getCenter(el , { left, top, width, height } = el.getBoundingClientRect()) {
let point = Object.assign(field.createSVGPoint(),{ x : left + width / 2, y : top + height / 2 }).matrixTransform(field.getScreenCTM().inverse());
return { cx:~~point.x, cy:~~point.y };
}
function runto(destination) {
let {cx:playerX,cy:playerY} = getCenter(player);
let {cx:baseX ,cy:baseY } = getCenter(field.getElementById(destination));
let translateX = baseX - playerX;
let translateY = baseY- playerY;
let track = `<line x1="${playerX}" y1="${playerY}" x2="${baseX}" y2="${baseY}" stroke-width="10" stroke="black"/>`;
field.insertAdjacentHTML("beforeend",track);
return player.animate([{
//transform: `translate(${0}px,${0}px)`
}, { transform: `translate(${translateX}px,${translateY}px)` }],
{ duration:500, fill:"forwards", composite:"add" }
).finished; // return Promise
}
runto("first").then(() => runto("second"));
<style>
#FIELD { background: lightgreen; width: 150px; margin: 20px }
</style>
<svg id=FIELD viewBox="0 0 250 250" transform="rotate(45)">
<g fill="brown">
<path id="home" d="M 150 150 h 80 v 80 h -80 v -80z" fill="green"/>
<path id="first" d="M 150 20 h 80 v 80 h -80 v -80z"/>
<path id="second" d="M 20 20 h 80 v 80 h -80 v -80z"/>
<path id="third" d="M 20 150 h 80 v 80 h -80 v -80z"/>
</g>
<circle id="player" cx="190" cy="190" r="30" fill="gold"/>
</svg>
You could call Animation#commitStyles() after the animation completes, so that its state is "written" to your player.
You now need to come back to the default composite: "replace" option, and you also need to modify your initial player settings so that it's positioned using CSS translate() instead of using its cx and cy values (or you could also modify your calculations so they return relative positions instead).
let field = document.getElementById("FIELD");
let player = field.getElementById("player");
function getCenter(el, { left, top, width, height } = el.getBoundingClientRect()) {
let point = Object.assign(field.createSVGPoint(), {
x: left + width / 2,
y: top + height / 2
}).matrixTransform(field.getScreenCTM().inverse());
return {
cx: ~~point.x,
cy: ~~point.y
};
}
async function runto(destination) {
let { cx: playerX, cy: playerY } = getCenter(player);
let { cx: baseX, cy: baseY } = getCenter(field.getElementById(destination));
let translateX = baseX - playerX;
let translateY = baseY - playerY;
let track = `<line x1="${playerX}" y1="${playerY}" x2="${baseX}" y2="${baseY}" stroke-width="10" stroke="black"/>`;
field.insertAdjacentHTML("beforeend", track);
const anim = player.animate([{
transform: `translate(${baseX}px,${baseY}px)`
}], {
duration: 500,
fill: "forwards"
});
await anim.finished;
anim.commitStyles(); // write the current state to the animated element
anim.cancel(); // no need to keep it around anymore
}
runto("first").then(() => runto("second"));
<style>
#FIELD {
background: lightgreen;
width: 150px;
margin: 20px
}
</style>
<svg id=FIELD viewBox="0 0 250 250" transform="rotate(45)">
<g fill="brown">
<path id="home" d="M 150 150 h 80 v 80 h -80 v -80z" fill="green"/>
<path id="first" d="M 150 20 h 80 v 80 h -80 v -80z"/>
<path id="second" d="M 20 20 h 80 v 80 h -80 v -80z"/>
<path id="third" d="M 20 150 h 80 v 80 h -80 v -80z"/>
</g>
<circle id="player" cx="0" cy="0" r="30" style="transform:translate(190px, 190px)" fill="gold"/>
</svg>

Adding an onClick event to an svg element in a java script class

I'm trying to add an onClick event to an svg tag where i want it to direct me to another page which is page2.html.
I really tried everything but I couldn't find a solution.
If there is another way of doing that other than using onClick would be great as well3
Would really appreciate the help.
Here is my code:-
import React from "react";
import "./HeaderBox.css";
import "./HeaderBox.scss";
import { Card } from "react-bootstrap";
const HeaderBox = () => {
return (
<Card id="Header">
<button>page</button>
<div class="HeaderDiv">Omar Fares</div>
<svg viewBox="45 60 400 320" xmlns="http://www.w3.org/2000/svg">
<path
fill="#fff"
d="M 90 210 C 90 180 90 150 90 150 C 150 150 180 150 180 150 C 180 150 300 150 300 150 C 300 150 330 150 390 150 C 390 150 390 180 390 210 C 390 240 390 270 390 270 C 330 270 300 270 300 270 C 300 270 180 270 180 270 C 180 270 150 270 90 270 C 90 270 90 240 90 210"
mask="url(#knockout-text)"
></path>
<mask id="knockout-text">
<rect width="100%" height="100%" fill="#fff" x="0" y="0" />
<text x="147" y="227" fill="#000">
Memory
</text>
</mask>
<a onClick="window.location.href='page2.html'"></a>
</svg>
</Card>
);
};
export default HeaderBox;
In React, you need to assign a function to onClick
onClick={() => window.location.href='page2.html'}

Rounded corners in SVG path semi circle

I have a path which is a semi-circle. How do I make the corners rounded? I do not want to use stroke-linecap: round as I need it to be rounded only on these corners:
<svg>
<g>
<!--background -->
<path fill="none" stroke-dasharray="" stroke-width="16" stroke="#607985" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!-- strokes -->
<path id="meter-back" fill="none" stroke-width="15" stroke="white" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!--progress -->
<path id="meter-fill" fill="none" stroke-dashoffset="219.942" stroke-dasharray="109.971, 109.971" stroke="rgba(96,121,133,0.7)" stroke-width="15" d="M30 100 A 40 40 0 0 1 170 100" stroke="#607985"></path>
</g>
</svg>
Here is a fixed solution. dividerPos can be in range from 0 to 1:
const getPath = (outerRadius, innerRadius, cornerRadius, dividerPos) => {
const angle = Math.PI * (1 - dividerPos);
const outerPointX = outerRadius * Math.cos(angle);
const outerPointY = outerRadius * -Math.sin(angle);
const innerPointX = innerRadius * Math.cos(angle);
const innerPointY = innerRadius * -Math.sin(angle);
const left = `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 0 0 1
${outerPointX},${outerPointY}
L ${innerPointX},${innerPointY}
A ${innerRadius},${innerRadius} 0 0 0 ${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
const right = `M ${outerPointX},${outerPointY}
A ${outerRadius},${outerRadius} 0 0 1
${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${innerPointX},${innerPointY}
Z`;
return {left, right};
};
const {left, right} = getPath(120, 90, 15, 0.5);
d3.select('.left').attr('d', left);
d3.select('.right').attr('d', right);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='grey' fill='grey' class='left'/>
<path stroke='grey' fill='none' class='right'/>
</g>
</svg>
Use getPath routine to compute the desired path
(The 0,0 point in the center of the semi-circle):
const getPath = (outerRadius, innerRadius, cornerRadius) => {
return `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 1 1 1 ${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
};
d3.select('path').attr('d', getPath(120, 90, 12));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='red' fill='none'/>
<circle fill='red' r='5' cx='0' cy='0'/>
</g>
</svg>

How to render n slices inside svg circle?

I am developing a reactJS app.
I need to render an svg circle and when I click it it spawns n equal slices inside.
I created the slices, here is the code
renderSlices = () => {
let slices = [];
const numberOfSlice = 12; //number of slices
for (let i = 0; i < numberOfSlice; i++) {
slices.push({ percent: 1 / numberOfSlice, color: 'gray' });
}
let cumulativePercent = 0;
let arr = [];
arr = slices.map(slice => {
const [startX, startY] = this.getCoordinatesForPercent(cumulativePercent.toString());
cumulativePercent += slice.percent;
const [endX, endY] = this.getCoordinatesForPercent(cumulativePercent.toString());
const largeArcFlag = slice.percent > 0.5 ? 1 : 0;
const pathData = [
`M ${startX} ${startY}`, // Move
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`, // Arc
'L 0 0', // Line
].join(' ');
return <path d={pathData} fill={slice.color} key={pathData} />;
});
return arr;
}
getCoordinatesForPercent(percent: string) {
const x = Math.cos(2 * Math.PI * parseFloat(percent));
const y = Math.sin(2 * Math.PI * parseFloat(percent));
return [x, y];
}
Render method:
<div className="container">
<svg
height="306"
width="306"
viewBox="-1 -1 2 2"
>
{/* <circle cx="150" cy="150" r="148" stroke="black"
strokeWidth="2" fill={"transparent"}/> */}
{this.renderSlices()}
</svg>
</div>
The problem is when I remove the comment from the circle tag and I remove the viewBox, only the circle show up, and when I comment the circle tag and put the viewBox, only the slices show up.
I would like to have the circle with a visible stroke and inside it the slices.
Any help please ?
EDIT:
<svg height="306" width="306" viewBox="0 0 306 306">
<path d="M 1 0 A 1 1 0 0 1 0.8660254037844387 0.49999999999999994 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.8660254037844387 0.49999999999999994 A 1 1 0 0 1 0.5000000000000001 0.8660254037844386 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.5000000000000001 0.8660254037844386 A 1 1 0 0 1 6.123233995736766e-17 1 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 6.123233995736766e-17 1 A 1 1 0 0 1 -0.4999999999999998 0.8660254037844387 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.4999999999999998 0.8660254037844387 A 1 1 0 0 1 -0.8660254037844385 0.5000000000000003 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.8660254037844385 0.5000000000000003 A 1 1 0 0 1 -1 5.66553889764798e-16 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -1 5.66553889764798e-16 A 1 1 0 0 1 -0.866025403784439 -0.4999999999999994 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.866025403784439 -0.4999999999999994 A 1 1 0 0 1 -0.5000000000000004 -0.8660254037844385 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.5000000000000004 -0.8660254037844385 A 1 1 0 0 1 -1.8369701987210297e-16 -1 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -1.8369701987210297e-16 -1 A 1 1 0 0 1 0.5000000000000001 -0.8660254037844386 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.5000000000000001 -0.8660254037844386 A 1 1 0 0 1 0.8660254037844388 -0.49999999999999967 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.8660254037844388 -0.49999999999999967 A 1 1 0 0 1 1 -2.4492935982947064e-16 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
</svg>
getCoordinatesForPercent(percent: string, radius: number, circle: {x: number, y: number}) {
const x = radius * Math.cos(2 * Math.PI * parseFloat(percent)) + circle.x;
const y = radius * Math.sin(2 * Math.PI * parseFloat(percent)) + circle.y;
return [x, y];
}
Multiply with radius and add the circle coordinates
and change A 1 1 0 ${largeArcFlag} to A ${radius} ${radius} 0 ${largeArcFlag} and 'L 0 0' to 'L ${circle.x} ${circle.y}'

How to detect which segment of a svg path is clicked in javascript?

SVG path element for example:
<path id="path1"
d="M 160 180 C 60 140 230 20 200 170 C 290 120 270 300 200 240 C 160 390 50 240 233 196"
stroke="#009900" stroke-width="4" fill="none"/>
It has 4 svg segments (3 curve segments in human eyes):
M 160 180
C 60 140 230 20 200 170
C 290 120 270 300 200 240
C 160 390 50 240 233 196
when click on the path, I get the x and y of mouse position, then how to detect which curve segment is clicked?
function isInWhichSegment(pathElement,x,y){
//var segs = pathElement.pathSegList; //all segments
//
//return the index of which segment is clicked
//
}
There are a few methods for SVGPathElements that you can use. Not really straighforward, but you could get the total length of your path, then check at every point of length the coordinates with getPointAtLength and compare it with coordinates of the click. Once you figure the click was at which length, you get the segment at that length with getPathSegAtLength. like that for example:
var pathElement = document.getElementById('path1')
var len = pathElement.getTotalLength();
pathElement.onclick = function(e) {
console.log('The index of the clicked segment is', isInWhichSegment(pathElement, e.offsetX, e.offsetY))
}
function isInWhichSegment(pathElement, x, y) {
var seg;
// You get get the coordinates at the length of the path, so you
// check at all length point to see if it matches
// the coordinates of the click
for (var i = 0; i < len; i++) {
var pt = pathElement.getPointAtLength(i);
// you need to take into account the stroke width, hence the +- 2
if ((pt.x < (x + 2) && pt.x > (x - 2)) && (pt.y > (y - 2) && pt.y < (y + 2))) {
seg = pathElement.getPathSegAtLength(i);
break;
}
}
return seg;
}
<svg>
<path id="path1" d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" stroke="#009900" stroke-width="4" fill="none" />
</svg>

Categories