<div class="container">
<div class="flex">
<div class="label">
<span class="number" data-anchor=".flex .number:eq(1)">1</span><span class="text">
<span class="title">Подготовка</span>планирование</span>
</div>
<div class="label">
<span class="number" data-anchor=".flex .number:eq(2)">2</span><span class="text">
<span class="title">Подготовка</span>планирование</span>
</div>
<div class="label">
<span class="number" data-anchor=".flex .number:eq(3)">3</span><span class="text">
<span class="title">Подготовка</span>планирование</span>
</div>
<div class="label">
<span class="number">4</span><span class="text">
<span class="title">Подготовка</span>планирование</span>
</div>
</div>
<canvas></canvas>
</div>
.container {
position: relative;
}
.container canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1001;
pointer-events: none;
}
.flex {
display: grid;grid-template-columns: auto;grid-gap: 80px 0;
background:gray;
width:60%; margin:0 auto;
}
.flex .label {
flex: auto;display: flex;overflow: hidden;
}
.label>.number {
display: inline-flex;
padding: 30px 40px;
border-radius: 40px;
background: blue;
}
.label>.text {
flex-direction: column;
display: inline-flex;
width: auto;
}
.label .title {
font-size:1.5em;
font-weight: 500;
}
.flex .label:nth-of-type(2) {
margin: 0 0 0 auto;
flex-direction: row-reverse;
}
.flex .label:nth-of-type(3) {
margin: 0 auto;
}
.flex .label:nth-of-type(4) {
margin: 0 auto;
flex-direction: row-reverse;
}
/**
* упрощенное рисование соединительных линий, без анимации и DD
* Объект, осуществляющий вычисления. Некоторые неявные типы
* -- <<point>>>> - [x,y] - координаты точки {x,y}
* -- <<vector>>>> - {start: <<point>>, fin: <<point>>}
* @type {{_interval: boolean, moveupto: (function(*, *): *[]), newline: anima.newline, permanentdraw: anima.permanentdraw, clearpermanent: anima.clearpermanent, moveuptopercent: (function(*, *): *[]), dist: (function(*): number), resize: anima.resize, _draw: anima._draw, _TO: boolean, draw: anima.draw, lines: *[]}}
*/
let anima = {
root: null,
canvas: null,
/* массив линий */
lines: [],
/* список дефолтных генераторов линий в порядке предпочтений */
currentline: [
'vhvC', // круглый path сверху вниз - вертикально
],
/**
* временные переменные
*/
_TO: false, _interval: false,
/**
* Инициирует непрерывную перерисовку контента. Для плавной анимации.
*/
permanentdraw: function () {
if (!!this._interval) return;
this._interval = setInterval(() => anima.draw(), 10);
},
/**
* останавливает анимацию
*/
clearpermanent: function () {
if (!this._interval) return;
clearInterval(this._interval);
this._interval = null;
},
/**
* отложенный draw - заявка на перерисовку. Можно частить, все равно не должно тормозить
*/
draw: function () {
if (!this._TO) {
let that = this;
this._TO = window.requestAnimationFrame(function () {
that._TO = false;
that._draw();
});
}
},
/* настоящая перерисовка канваса */
_draw: function () {
let canvas = this.canvas, d = new Date();
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let l of this.lines) {
for (let a of l.a) {
if (a.a === 'path') {
//console.log(percent, l.dist, alength);
ctx.setLineDash([5, 3]);
ctx.strokeStyle = "white";
ctx.lineWidth = 3;
ctx.stroke(a.path);
}
}
}
this.clearpermanent();
},
/**
* обработка ресайза, просто установка габаритов канваса.
* @param bound
*/
resize: function () {
let bound = this.root.getBoundingClientRect()
this.canvas.setAttribute("height", bound.height);
this.canvas.setAttribute("width", bound.width);
},
todom: function (el) {
if (typeof el === 'string' || el instanceof String) {
let m = el.match(/^(.*?)\:eq\((\d)\)$/);
if (!!m) {
el = document.querySelectorAll(m[1])[m[2]];
} else {
el = document.querySelector(el);
}
}
return el;
},
vhvC: {
allow: function (start, fin, border) {
return true;
},
// прорисовываем гладкую кривую с помощью кривых Безье
produce: function (line, start, fin, border, animation) {
let xstart = [start.left - border.left + start.width / 2, start.top - border.top + start.height],
ystart = [fin.left - border.left + fin.width / 2, fin.top - border.top];
if (xstart[1] >= ystart[1]) {
xstart = [start.left - border.left + start.width / 2, start.top - border.top];
ystart = [fin.left - border.left + fin.width / 2, fin.top - border.top + fin.height];
}
// генерация точки, от start в направлении vect расстояния disp.
function v(start, vect, disp) {
if (!!vect)
return (start[0] + disp * vect[0]).toFixed(2) + ' '
+ (start[1] + disp * vect[1]).toFixed(2) + ' ';
else
return (start[0]).toFixed(2) + ' '
+ (start[1]).toFixed(2) + ' ';
}
let vect = [Math.sin(Math.PI / 2 + 10 * Math.PI / 180), Math.cos(Math.PI / 2 + 10 * Math.PI / 180)],
center = [(xstart[0] + ystart[0]) / 2, (xstart[1] + ystart[1]) / 2],
dist = xstart[0] - ystart[0];
if (dist > 0) {
vect[1] = -vect[1];
}
// очень мутная схема генерации 2-x кривых
let p = new Path2D("M" + v(xstart) + "C "
+ v(center, vect, dist / 2)
+ v(center, vect, dist / 4)
+ v(center)
+ v(center, vect, -dist / 4)
+ v(center, vect, -dist / 2)
+ v(ystart)
);
line.a.push({a: 'path', path: p, animation: 100});
}
},
/**
* добавить еще один элемент анимации
* @param from - array|HTMLDomElement
* @param to - DOM
* @param animation - int: микротик - 1000 в секунду.
*/
newline: function (from, to, animation) {
from = this.todom(from);
to = this.todom(to);
let start = from.getBoundingClientRect();
let fin = to.getBoundingClientRect();
let border = this.root.getBoundingClientRect();
let line = {a: []}, dist = 0;
for (const cl of this.currentline) {
if (this[cl].allow(start, fin, border)) {
this[cl].produce(line, start, fin, border, animation);
break;
}
}
this.lines.push(line);
this.permanentdraw();
}
}
$(function () {
anima.root = $('.container')[0];
anima.canvas = $('.container canvas')[0];
/**
* Изменение окна броузера
*/
function aredraw() {
anima.lines = []; // пока вот так вот просто, без пересчета
$('[data-anchor]').each(function () {
if ($(this).data('complete')) {
let x = $(this).data('anchor').split(';');
for (const xx of x)
anima.newline(this, xx);
}
})
anima.draw();
}
$(window).on('resize', function () {
anima.resize()//
anima.lines = [];
$('[data-anchor]').each(function () {
let x = $(this).data('anchor').split(';');
for (const xx of x)
anima.newline(this, xx);
})
}).trigger('resize');
$('[data-anchor]').each(function () {
let x = $(this).data('anchor').split(';');
for (const xx of x)
anima.newline(this, xx, 1000);
})
})
This Pen doesn't use any external CSS resources.