SVG - Convert all shapes/primitives to <path> - d3.js
I'm doing a number of D3.JS operations requiring that I work with SVG paths instead of primitives/shapes (polylines, recs, etc.).
This question is general, but I'd like to know if it is possible to convert any SVG primitive to a path, either with D3 or another script/library.
For reference, here is a link which does it for polylines: https://gist.github.com/andytlr/9283541
I'd like to do this for every primitive. Any ideas? Is this possible?
JavaScript solution
You can also convert all primitives using Jarek Foksa's path-data polyfill:
It's main purpose is to parse a path's d attribute to an array of commands.
But it also provides a normalize method to convert any element's geometry to path commands.
element.getPathData({normalize: true});
This method will convert all commands and coordinates to absolute values
and reduce the set of commands to these cubic bézier commands: M,L, C, Z.
Example usage: Convert all primitives
(and convert other commands A, S, Q etc.)
const svgWrp = document.querySelector('.svgWrp');
const svg = document.querySelector('svg');
const primitives = svg.querySelectorAll('path, line, polyline, polygon, circle, rect');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = svgWrp.innerHTML;
function convertPrimitives(svg, primitives) {
primitives.forEach(function(primitive, i) {
/**
* get normalized path data:
* all coordinates are absolute;
* reduced set of commands: M, L, C, Z
*/
let pathData = primitive.getPathData({
normalize: true
});
//get all attributes
let attributes = [...primitive.attributes];
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
setAttributes(path, attributes, exclude);
// set d attribute from rounded pathData
path.setPathData(roundPathData(pathData, 1));
svg.appendChild(path);
primitive.remove();
})
// optional: output new svg markup
let newSvgMarkup = svgWrp.innerHTML.
replaceAll("></path>", "/>").
replace(/([ |\n|\r|\t])/g, " ").
replace(/ +/g, ' ').trim().
replaceAll("> <", "><").
replaceAll("><", ">\n<");
svgMarkup.value = newSvgMarkup;
}
function roundPathData(pathData, decimals = 3) {
pathData.forEach(function(com, c) {
let values = com['values'];
values.forEach(function(val, v) {
pathData[c]['values'][v] = +val.toFixed(decimals);
})
})
return pathData;
}
function setAttributes(el, attributes, exclude = []) {
attributes.forEach(function(att, a) {
if (exclude.indexOf(att.nodeName) === -1) {
el.setAttribute(att.nodeName, att.nodeValue);
}
})
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill#1.0.3/path-data-polyfill.min.js"></script>
<p><button type="button" onclick="convertPrimitives(svg, primitives)">Convert Primitives</button></p>
<div class="svgWrp">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 30">
<polygon id="polygon" fill="#ccc" stroke="green" points="9,22.4 4.1,14 9,5.5 18.8,5.5 23.7,14
18.8,22.4 " />
<polyline id="polyline" fill="none" stroke="red" points="43,22.4 33.3,22.4 28.4,14 33.3,5.5 43,5.5
47.9,14 " />
<rect id="rect" x="57.3" y="5.5" fill="none" stroke="orange" width="16.9" height="16.9" />
<line id="line" fill="none" stroke="purple" x1="52.6" y1="22.4" x2="52.6" y2="5.5" />
<circle class="circle" data-att="circle" id="circle" fill="none" stroke="magenta" cx="87.4" cy="14" r="8.5" />
<path transform="scale(0.9) translate(110,5)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z" fill="red" class="segment segment-1 segment-class" id="segment-01"/>
</svg>
</div>
<h3>Svg markup</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>
The above example script will also retain all attributes like class, id, fill etc.
But it will strip attributes like r, cx, rx specific to primitives.
Do we need this polyfill?
Unfortunately, the getPathData() and setPathData() methods are still a svg 2 drafts/proposals – intended to replace the deprecated pathSegList() methods.
Hopefully we will get native browser support in the near future.
Since this polyfill is still rather lightweight (~12.5 KB uncompressed) compared to more advanced svg libraries like (snap.svg, d3 etc.) it won't increase your loading times significantly.
Update: Standalone script (no polyfill dependency)
This is rather a proof of concept – you can convert svg primitives based on pretty basic value calculations – without the need of advanced frameworks/libraries – inspired by this post: Convert all shapes/primitives into path elements of SVG.
But as I fiddled around with my own clunky conversion script, I quickly realised that there were some challenges (that Jarek Foksa's normalizing implementations solves flawlessly) such as:
Relative i.e percentage based units
<circle cx="50%" cy="50%" r="25%" />
OK ... I guess we need to calculate these relative values to absolute coordinates according to the parent svg's boundaries as defined by viewBox property ... maybe no viewBox available at all ... or width/height values.
Or something like rx, ry properties to apply rounded borders to a <rect> element – for a decent conversion we'll need to add some curvy commands like a, c or s.
Paths vs. primitives
It's true that a <path> element can draw just any shape a primitive can offer via cubic or quadratic spline commands – even in a more efficient way due to it's concatenating abilities (combining multiple shapes) and furthermore its relative or shorthand commands.
But it doesn't support relative units – however the shapes you need to convert might heavily depend on relative dimensions (e.g. circular gauges pie charts etc.)
Conclusion
It's not too difficult to write your custom conversion script, but pay attention to some tricky details.
const svg = document.querySelector('svg');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = svg.outerHTML;
/**
* example script
**/
function getConvertedMarkup(svg, markupEl, decimals = 1) {
convertPrimitivesNative(svg, decimals);
//optimize output
let newSvgMarkup = svg.outerHTML.
replaceAll("></path>", "/>").
replace(/^\s+|\s+$|\s+(?=\s)/g, "").
replaceAll("> <", "><").
replaceAll("><", ">\n<");
markupEl.value = newSvgMarkup;
}
/**
* parse svg attributes and convert relative units
**/
function parseSvgAttributes(svg, atts) {
let calcW = 0;
let calcH = 0;
let calcR = 0;
//1. check viewBox
let viewBoxAtt = svg.getAttribute('viewBox');
let viewBox = viewBoxAtt ? viewBoxAtt.split(' ') : [];
[calcW, calcH] = [viewBox[2], viewBox[3]];
//2. check width attributes
if (!calcW || !calcH) {
widthAtt = svg.getAttribute('width') ? parseFloat(svg.getAttribute('width')) : '';
heightAtt = svg.getAttribute('height') ? parseFloat(svg.getAttribute('height')) : '';
[calcW, calcH] = [widthAtt, heightAtt];
}
//3. calculate by getBBox()
if (!calcW || !calcH) {
let bb = svg.getBBox();
[calcW, calcH] = [(calcW ? calcW : bb.width), (calcH ? calcH : bb.height)];
}
// calculate relative radius: needed for non square aspect ratios
calcR = Math.sqrt(Math.pow(calcW, 2) + Math.pow(calcH, 2)) / Math.sqrt(2);
let attArr = [...atts];
let attObj = {};
attArr.forEach(function(att) {
let attName = att.nodeName;
// convert percentages to absolute svg units
let val = att.nodeValue;
let percentAtts = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'r', 'rx', 'ry', 'cx', 'cy', 'width', 'height']
if (val.toString().indexOf('%') !== -1 && percentAtts.indexOf(attName) !== -1) {
// strip units
val = parseFloat(val);
switch (attName) {
case 'cx':
case 'rx':
case 'width':
case 'x':
case 'x1':
case 'x2':
val = 1 / 100 * val * calcW;
break;
case 'cy':
case 'ry':
case 'height':
case 'y':
case 'y1':
case 'y2':
val = 1 / 100 * val * calcH;
break;
case 'r':
val = 1 / 100 * val * calcR;
break;
}
}
attObj[att.nodeName] = val;
});
return attObj;
}
/**
* convert primitive attributes to relative path commands
*/
function convertPrimitivesNative(svg, decimals = 3) {
let primitives = svg.querySelectorAll('line, polyline, polygon, circle, ellipse, rect');
if (primitives.length) {
primitives.forEach(function(primitive) {
let pathData = [];
let type = primitive.nodeName;
let atts = parseSvgAttributes(svg, primitive.attributes, 2);
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
switch (type) {
case 'rect':
let [rx, ry] = [atts.rx, atts.ry];
rx = !rx && ry ? ry : rx;
ry = !ry && rx ? rx : ry;
let [x, y, width, height] = [atts.x, atts.y, atts.width, atts.height];
let [widthInner, heightInner] = [width - rx * 2, height - ry * 2];
if (rx) {
pathData.push({
type: 'M',
values: [x, (y + ry)]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'h',
values: [widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'v',
values: [heightInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'h',
values: [-widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'z',
values: []
});
} else {
pathData.push({
type: 'M',
values: [x, y]
}, {
type: 'h',
values: [width]
}, {
type: 'v',
values: [height]
}, {
type: 'h',
values: [-width]
}, {
type: 'z',
values: []
});
}
break;
case 'line':
let [x1, y1, x2, y2] = [atts.x1, atts.y1, atts.x2, atts.y2];
pathData.push({
type: 'M',
values: [x1, y1]
}, {
type: 'l',
values: [(x2 - x1), (y2 - y1)]
});
break;
case 'circle':
case 'ellipse':
if (type == 'circle') {
let r = atts.r;
let [cX, cY] = [atts.cx, atts.cy - atts.r];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, -r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, -r]
}, {
type: 'z',
values: []
});
} else {
let rx = atts.rx;
let ry = atts.ry;
let [cX, cY] = [atts.cx, atts.cy - atts.ry];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'z',
values: []
});
}
break;
case 'polygon':
case 'polyline':
let closePath = type == 'polygon' ? 'z' : '';
let points = atts.points.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let pointArr = points.split(' ');
pathData.push({
type: 'M',
values: [+pointArr[0], +pointArr[1]]
});
for (let i = 2; i < pointArr.length; i += 2) {
let [x0, y0] = [+pointArr[i - 2], +pointArr[i - 1]];
let [x, y] = [+pointArr[i], +pointArr[i + 1]];
let com = {};
if (y == y0) {
com = {
type: 'h',
values: [x - x0]
}
} else if (x == x0) {
com = {
type: 'v',
values: [y - y0]
}
} else {
com = {
type: 'l',
values: [x - x0, y - y0]
}
}
pathData.push(com);
}
if (closePath) {
pathData.push({
type: 'z',
values: []
});
}
break;
//paths
default:
let dClean = atts.d.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let dArr = dClean.replace(/([a-zA-Z])/g, " | $1").split(' | ');
dArr.shift();
for (let i = 0; i < dArr.length; i++) {
let command = dArr[i].trim().split(' ');
let type = command.shift();
command = command.map((x) => {
return parseFloat(x);
});
pathData.push({
type: type,
values: command
});
}
break;
}
// copy primitive's attributes to path
setAttributes(path, atts, exclude);
// round coordinates and replace primitive with path
path.setPathDataOpt(pathData, decimals);
primitive.replaceWith(path);
})
}
};
function setAttributes(el, attributes, exclude = []) {
for (key in attributes) {
if (exclude.indexOf(key) === -1) {
el.setAttribute(key, attributes[key]);
}
}
}
function getAttributes(el) {
let attArr = [...el.attributes];
let attObj = {};
attArr.forEach(function(att) {
attObj[att.nodeName] = att.nodeValue;
});
return attObj;
}
/**
* return rounded path data
* based on:
* https://github.com/jarek-foksa/path-data-polyfill/blob/master/path-data-polyfill.js
*/
if (!SVGPathElement.prototype.setPathDataOpt) {
SVGPathElement.prototype.setPathDataOpt = function(pathData, decimals = 3) {
let d = "";
if (pathData.length) {
for (let i = 0; i < pathData.length; i++) {
let seg = pathData[i];
let [type, values] = [seg.type, seg.values];
let valArr = [];
if (values.length) {
for (let v = 0; v < values.length; v++) {
val = parseFloat(values[v]);
valArr.push(+val.toFixed(decimals));
}
}
d += type;
if (valArr.length) {
d += valArr.join(" ").trim();
}
}
d = d.
replaceAll(' -', '-').
replaceAll(' 0.', ' .').
replaceAll(' z', 'z');
this.setAttribute("d", d);
}
};
}
<p><button type="button" onclick="getConvertedMarkup(svg, svgMarkup, 2)">Convert Primitives</button></p>
<svg xmlns="http://www.w3.org/2000/svg" data-width="150px" data-height="30px" viewBox="0 0 150 30">
<polygon id="polygon" fill="#CCCCCC" stroke="#E3000F" points="7.9,22.8 3,14.3 7.9,5.8 17.6,5.8 22.5,14.3
17.6,22.8 " />
<polyline id="polyline" fill="none" stroke="#E3000F" points="40.9,22.8 31.1,22.8 26.2,14.3 31.1,5.8
40.9,5.8 45.8,14.3 " />
<rect id="rect" x="37.5%" y="20%" rx="2%" ry="5%" fill="none" stroke="#E3000F" width="6%" height="56%" />
<line id="line" fill="none" stroke="#E3000F" x1="50.5" y1="22.8" x2="52.5" y2="5.8" />
<circle id="circle" fill="none" stroke="#E3000F" cx="52%" cy="49%" r="8%" />
<ellipse id="ellipse" fill="none" stroke="#E3000F" cx="68%" cy="49%" rx="7%" ry="25%" />
<path id="piechart" transform="scale(0.9) translate(130, 6)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z"
fill="red" class="segment segment-1 segment-class" id="segment-01" />
</svg>
<h3>Output</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>
Codepen converter example
I found this github site which has a set of java functions for converting shapes to paths: https://github.com/JFXtras/jfxtras-labs/blob/2.2/src/main/java/jfxtras/labs/util/ShapeConverter.java
Related
Three.js drag a model on x and z axis. React three fiber
I am trying to make models draggable in three.js. I want my model to follow my mouse when I move it. This is what I am trying to accomplish. I am using react-three-fiber and #use-gesture/react What I am trying to accomplish Here is how my program looks My program The difference is quite noticeable. On the good example, the model follows the mouse wherever it goes. On my program, that is not the case. Here is my code for the cube const BasicOrangeBox = ({setControlsDisabled, startPosition} : BasicOrangeBoxType) => { const { camera } = useThree(); const [boxPosition, setBoxPosition] = useState(startPosition) const bind = useGesture({ onDrag: ({movement: [x, y]}) => { setControlsDisabled(true); setBoxPosition( (prev) => { const newObj = {...prev}; newObj.x = newObj.x0 + (x/100); newObj.z = newObj.z0 + (y/100); return newObj; } ) }, onDragEnd: () => { setControlsDisabled(false); setBoxPosition( (prev) => { const newObj = {...prev}; newObj.x0 = newObj.x; newObj.z0 = newObj.z; return newObj; } ) } }) return ( <mesh {...bind() } position={[boxPosition.x, boxPosition.y, boxPosition.z]} > <boxGeometry /> <meshBasicMaterial color={"orange"} /> </mesh> ) }
Here is how I made a mesh cube draggable only on x and z axis like in a video. Needed packages: three react-three/fiber use-gesture/react First, I created a plane that spanned across my whole viewport and assigned it to a useRef <mesh rotation={[MathUtils.degToRad(90), 0, 0]} ref={planeRef} position={[0, -0.01, 0]}> <planeGeometry args={[innerWidth, innerHeight]} /> <meshBasicMaterial color={0xfffffff} side={DoubleSide} /> </mesh> Then I added that ref to a useContext so I can use it in different components. Next, I imported raycaster from useThree hook and planeRef from aforementioned useContext. Then I used useGesture onDrag and onDragEnd to enable and disable my OrbitControls Inside the onDrag, I used raycaster's intersectsObject method and added an array of only one element, my plane, as a parameter. This gave me x, y, z coordinates where my mouse intersects with the plane. (Y is always 0) Then I updated my box position. Here is the full code snippet const BasicOrangeBox = ({setControlsDisabled, startPosition} : BasicRedBoxType) => { const { raycaster } = useThree(); const [boxPosition, setBoxPosition] = useState(startPosition); const planeRef = useContext(PlaneContext); const bind = useGesture({ onDrag: () => { const intersects = raycaster.intersectObjects([planeRef]); if (intersects.length > 0){ const intersection = intersects[0]; console.log(intersection.point.x); setBoxPosition({ x: intersection.point.x, y: intersection.point.y, z: intersection.point.z, }) } setControlsDisabled(true); }, onDragEnd: () => { setControlsDisabled(false); } }) return ( //#ts-ignore Ignores type error on next line <mesh {...bind() } position={[boxPosition.x, boxPosition.y, boxPosition.z]} > <boxGeometry /> <meshBasicMaterial color={"orange"} /> </mesh> ) }
Analyzing songfile using fft when i set the song volume to 0 in p5.js
In p5.js, I am using "new p5.FFT(x,y)" to analyze the amplifer mp3 file. But this has a little problem that if you set the mp3's volume to 0(by using .setVolume(x)) the song file cannot be analyzed maybe because you set volume to 0 so there's no input. So i want to know how to analyze songfile even when i set the volume to 0.
The trick is you need to connect your FFT at a point where the volume is still non-zero, and then have a node down stream where you control the volume. Here's an example where I've used the p5.EQ effect to control the volume of one part of the audio graph. The "Tada" sound is connected to an FFT and to the "mute" p5.EQ effect. This makes it so that the FFT visualizes the sound at full volume, but the slider controls how loud the sound actually is. The "Ding" sound on the other hand is connected directly to the output, no FFT, no volume control. let tada, ding; let tadaBtn, dingBtn; let volSlider; let fft; let mute; function preload() { tada = loadSound('https://www.paulwheeler.us/files/TADA.WAV'); ding = loadSound('https://www.paulwheeler.us/files/DING.WAV'); } function setup() { createCanvas(windowWidth, windowHeight); let controls = createElement('div'); controls.style('display', 'flex'); controls.position(10, 10); tadaBtn = createButton('Tada'); tadaBtn.mouseClicked(() => { if (!tada.isPlaying()) { tada.play(); tadaBtn.html('Stop'); } else { tada.stop(); } }); tadaBtn.parent(controls); tada.onended(() => { tadaBtn.html('Tada'); }); dingBtn = createButton('Ding'); dingBtn.mouseClicked(() => { if (!ding.isPlaying()) { ding.play(); dingBtn.html('Stop'); } else { ding.stop(); } }); dingBtn.parent(controls); ding.onended(() => { dingBtn.html('Ding'); }); volSlider = createSlider(0, 1, 0, 0); volSlider.input(() => { mute.amp(volSlider.value()); }); volSlider.parent(controls); tada.disconnect(); fft = new p5.FFT(); fft.setInput(tada); mute = new p5.EQ(); mute.amp(volSlider.value()); tada.connect(mute); mute.connect(); } function draw() { background(0); drawSpectrumGraph(0, 0, width, height); } // Graphing code adapted from https://jankozeluh.g6.cz/index.html by Jan Koželuh function drawSpectrumGraph(left, top, w, h) { let spectrum = fft.analyze(); stroke('limegreen'); fill('darkgreen'); strokeWeight(1); beginShape(); vertex(left, top + h); for (let i = 0; i < spectrum.length; i++) { vertex( left + map(log(i), 0, log(spectrum.length), 0, w), top + map(spectrum[i], 0, 255, h, 0) ); } vertex(left + w, top + h); endShape(CLOSE); } <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
Creating a histogram chart for each category
I am trying to create histogram having ages in groups like this (0-10), (10-20), ...(90,100) Dataset look like this: 0: {agebracket: "20", currentstatus: "Recovered", dateannounced: "30/01/2020"} 1: {agebracket: "45", currentstatus: "Confirmed", dateannounced: "02/03/2020"} 2: {agebracket: "24", currentstatus: "Recovered", dateannounced: "02/03/2020"} . . . 99: {agebracket: "58", currentstatus: "Hospitalized", dateannounced: "20/03/2020"} I was able to create histogram but that was on the whole dataset. I didn't take account of "currentstatus" --> "Recovered", "Hospitalized", "Deceased" On whole dataset: I tried to create histogram by currentstatus but it look like this: This is what I have tried: var binwidth = 10; var dim = cf.dimension(function(d) { return parseInt(d.agebracket); }); var age_by_cases= dim.group().reduce( // add (p, v) => { p[v.currentstatus] = (p[v.currentstatus] || 0) + 1; return p; }, // remove (p, v) => { p[v.currentstatus] -= 1; return p; }, // init () => ({}) ); barChart .height(300) .width(500) //give it a width .dimension(dim) .group(age_by_cases, type) .elasticY(true) .valueAccessor(function(p) { return p.value[type_c]; // return (binwidth * Math.floor(parseInt(p.value[type_c])/binwidth)) ; }) .x(d3.scaleLinear().domain([1,101])) .xUnits(dc.units.fp.precision(binwidth)) .elasticX(true); Line no. 170-184 and Line no. 227-243 https://blockbuilder.org/ninjakx/8e2c0b407fdb1991c9cc5e81e447ebe2 I just got struck at this badly. I don't know how to solve it.
Usually you will define the binning in the dimension key function: var dim = cf.dimension(function(d) { return binwidth * Math.floor(parseInt(p.agebracket)/binwidth); }; And then, use the bin width (or other binning spec) in xUnits, as you have it: .xUnits(dc.units.fp.precision(binwidth)) This causes crossfilter to sort each row into the bin with the value rounded down by binwidth, and it tells dc.js to calculate the bar width also using the binwidth.
I want change scrollview rolling speed in react native
Now I use interval make it come true, but it is very incoherence. If I can just change the method (scroll) speed, it well be nice. this.interval = setInterval(()=>{ if(!_scroll){ _this.interval && clearInterval(_this.interval); } if(totalWide+ScreenWidth >= width ){ _scroll.scrollWithoutAnimationTo(); totalWide=0; i=0; }else{ _scroll.scrollTo({x:eachWide*i,animate:true}); totalWide = totalWide + eachWide; i= i+1; } },250)
use decelerationRate property of ScrollView <ScrollView decelerationRate={0.5}> </ScrollView>
I got this working by having setInterval call a function(in which you define the logic or the pace at which the scroll should move). this.interval= setInterval(this.scrollwithSpeed, 100); // Set the function to this timer scrollwithSpeed() { position = this.state.currentPosition + x; // x decides the speed and currentPosition is set to 0 initially. this.scrollObject.scrollTo( { y: position, animated: true } ); this.setState({ currentPosition: position }); } Make sure you call clearInterval(this.interval) after it is done.
I would suggest to attach to js requestAnimationFrame (from how far I know it is supported in React Native). Bellow example will scroll linearly from top to bottom. If You need to scoll to different offset just change distance variable. startingPoint variable is redundant in scrolling from top to bottom but will stay in example. scroll() { if (this.scrollAnimationFrame) { cancelAnimationFrame(this.scrollAnimationFrame); } this.listRef.scrollToOffset({offset: 0, animated: false}); // remove if You don't start scroll from top const duration = this.scrollTime, startingPoint = 0, // change if You don't start scroll from top distance = Scrolling.LINE_HEIGHT * Scrolling.ITEMS_COUNT; let startTimestamp, progress; const frameCallback = (timestamp) => { if (!startTimestamp) { startTimestamp = timestamp; } progress = timestamp - startTimestamp; this.listRef.scrollToOffset({ offset: distance * (progress / duration) + startingPoint, animated: false, }); if (progress < duration) { this.scrollAnimationFrame = requestAnimationFrame(frameCallback); } }; this.scrollAnimationFrame = requestAnimationFrame(frameCallback); }
You can use reanimated to make it work. const offsetY = useSharedValue(0); const animatedProps = useAnimatedProps<FlatListProps<unknown>>(() => { return { contentOffset: { x: 0, y: offsetY.value, }, }; }); const handleScroll = () => { offsetY.value = withTiming(targetIndex * CARD_HEIGHT, { duration: YOUR_DURATION_HERE, }); } return <Animated.FlatList animatedProps={animatedProps} ... />
AnimationHandler has changed for threejs, how can I get it working?
I created a webgl animation using the threejs library, some time ago, today I have been trying to get it to run on the latest version of the three.js library. In the latest library, there is no pathcontrols.js. I copied pathcontrols.js from my old library to the new version, but this has not solved the problem, Below is the full function that does not work properly, but it is the .add() and the animation() calls where I think the problem is: function initAnimationPath( parent, spline, name, duration ) { var animationData = { name: name, fps: 0.6, length: duration, hierarchy: [] }; var i, parentAnimation, childAnimation, path = spline.getControlPointsArray(), sl = spline.getLength(), pl = path.length, t = 0, first = 0, last = pl - 1; parentAnimation = { parent: -1, keys: [] }; parentAnimation.keys[ first ] = { time: 0, pos: path[ first ], rot: [ 0, 0, 0, 1 ], scl: [ 1, 1, 1 ] }; parentAnimation.keys[ last ] = { time: duration, pos: path[ last ], rot: [ 0, 0, 0, 1 ], scl: [ 1, 1, 1 ] }; for ( i = 1; i < pl - 1; i++ ) { t = duration * sl.chunks[ i ] / sl.total; parentAnimation.keys[ i ] = { time: t, pos: path[ i ] }; } animationData.hierarchy[ 0 ] = parentAnimation; THREE.AnimationHandler.add( animationData ); return new THREE.Animation( parent, name, THREE.AnimationHandler.CATMULLROM_FORWARD, false ); }; it seems that the deprecation of animationhandler.add() seems to be the issue, but I am having difficulty working out how to replace it. (amongst many other things) I tried replacing animationhandler line and animation line with this: THREE.AnimationHandler.update(delta); return new THREE.Animation( camera, animationData); No joy. I would like to know how to get the animation started?? Thanks