My goal is to create something like this:
The yellow boxes represent divs whose number is dynamic. I need to generate a bezier curve whose loops are fixed below the image (red) since the position can change depending on the screen width. In addition, an element (black) follows the path once it reaches 50vh.
I found an npm package called svg-path-generator I could use to generate the path, but I don't know how to tackle this: https://www.npmjs.com/package/svg-path-generator
This could be a way to let the element follow the path: https://animejs.com/documentation/#motionPath
How could I approach this?
This is the HTML I have so far with a working code pen: https://codepen.io/marcoluzi/pen/ZEjBBVo
.block {
position: relative;
padding-top: 128px;
}
.block__line-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.block__line-wrapper svg {
width: 100%;
height: 100%;
}
.block-item {
display: grid;
gap: 32px;
grid-template-columns: 1fr;
align-items: center;
}
@media (min-width: 768px) {
.block-item {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 1024px) {
.block-item {
grid-template-columns: 1fr 4fr 2fr 4fr 1fr;
}
}
.block-item:not(:first-child) {
margin-top: 96px;
}
@media (min-width: 768px) {
.block-item:not(:first-child) {
margin-top: 144px;
}
}
@media (min-width: 1024px) {
.block-item:not(:first-child) {
margin-top: 192px;
}
}
@media (min-width: 1024px) {
.block-item:nth-child(odd) .block-item__image-wrapper {
grid-column: 2/4;
}
.block-item:nth-child(odd) .block-item__content {
grid-column: 4/-1;
}
}
@media (min-width: 768px) {
.block-item:nth-child(even) .block-item__image-wrapper {
order: 2;
}
.block-item:nth-child(even) .block-item__content {
order: 1;
}
}
@media (min-width: 1024px) {
.block-item:nth-child(even) .block-item__image-wrapper {
grid-column: 3/-2;
}
.block-item:nth-child(even) .block-item__content {
grid-column: 1/3;
}
}
.block-item__content {
display: flex;
flex-direction: column;
gap: 8px;
justify-content: center;
align-items: center;
}
.block-item__content > * {
margin: 0;
width: 100%;
}
@media (min-width: 768px) {
.block-item__content > * {
max-width: 416px;
}
}
<div >
<div >
<figure >
<picture>
<img src="https://via.placeholder.com/639x354" width="641" height="354" />
</picture>
</figure>
<div >
<h2 >Title 1</h2>
<p >Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>
<div >
<figure >
<picture>
<img src="https://via.placeholder.com/639x354" width="641" height="354" />
</picture>
</figure>
<div >
<h2 >Title 2</h2>
<p >Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>
<div >
<figure >
<picture>
<img src="https://via.placeholder.com/639x354" width="641" height="354" />
</picture>
</figure>
<div >
<h2 >Title 3</h2>
<p >Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>
<div >
<svg>
<path/>
</svg>
</div>
</div>
CodePudding user response:
Not an answer, just to get your creative juices flowing you can position elements without complex coding
<script>
customElements.define("svg-path-elements",class extends HTMLElement{
connectedCallback(){
let id = "curve" this.getAttribute("id");
let speed = 1; // set to 0.0001 for "instant" display
let position = 0; // position of first element
let elements = Array(~~this.getAttribute("count")||5).fill(0).map((_,idx,arr)=>{
let inlineFunctionOnEnd = `this.closest('svg').parentNode.onend(${idx})`;
let circle = `<circle id="circle${idx}" cx="0" cy="0" r="20" fill="green">
<animateMotion dur="${speed}s" keyPoints="0;${position}"
onend="${inlineFunctionOnEnd}"
fill="freeze" keyTimes="0;1" calcMode="linear">
<mpath href="#${id}"></mpath>
</animateMotion>
</circle>`
position = 1/(arr.length-1);
return circle;
}).join("");
this.innerHTML = `<svg viewBox="-50 50 1300 200" style="background:pink">
<path id="${id}" fill="none" stroke="red" stroke-width="5"
d="M 0 150
q 150 -150 300 0
q 150 150 300 0
q 150 -150 300 0
q 150 150 300 0"></path>${elements}</svg>`;
}
onend(idx){
let circle = this.querySelector("#circle" idx);
//console.log("positioned", this.id, circle.id );
}
})</script>
<svg-path-elements id="FIVE" count="5"></svg-path-elements>
<svg-path-elements id="TEN" count="10"></svg-path-elements>
CodePudding user response:
Don't know how to answer this, but I may be able to contribute - https://bleeptrack.de this website does something similar to what you want to do I think, with each point the line goes to being where you clicked instead of where the next div is. She just does some fanciness and loopings along the way, and more lines. She does this by creating an svg of the line using paper.js and then animating it using css. Hope that helps you somehow and you figure something out, would also be interested in the solution.