Строим контуры по опорным точкам на canvas
Построив полиномы по упорядоченным значениям [x, y]
, добавим ещё случай, когда опорные точки для кривых Безье (а другого готового механизма в JS особо и нет) не обязаны быть упорядоченными по возрастанию x
, то есть, задача интерполяции не ставится.
Вместо этого, задав массив points
, состоящий из подмассивов, описывающих отдельные контуры как списки пар значений [x, y]
координат опорных точек на canvas
, мы можем соединить эти точки гладкой кривой в том порядке, в каком они перечислены в списке. Не вдаваясь в теорию, уточним, что итоговая кривая будет проходить через первую и последнюю точки контура, но не все промежуточные.
Линиям легко задать любую толщину, цвет, стиль и даже градиент, но нам хотелось сделать картинку ещё и "переливающейся". Для этого основной код вынесен в метод animateGradient
, самовызывающийся по таймеру и с помощью пары переменных offset
и delta
управляющий промежуточным цветом градиента контуров (addColorStop).
Замкнутые контуры (у нас только "0") и тонкие линии (у нас толстая, 30 пикселей) анимируются таким подходом лучше. У нас цвет "доходит не до конца" именно из-за толщины линии. Далее показан скрипт в работе и код (без стандартных обрамляющих тегов HTML).
<div style="width: 600px; margin: 0 auto;"> <canvas id="myCanvas24" width="570" height="200" style="border: 1px dotted black;"></canvas> </div> <script> const canvas = document.getElementById("myCanvas24"); const ctx = canvas.getContext("2d"); const points = [ //опорные точки на канве [[20, 30], [95, 20], [95, 95], [20, 95], [20, 175], [105, 175]], [[220, 25], [245, 25], [245, 175], [170, 175], [170, 25], [220, 25]], [[320, 25], [395, 15], [395, 95], [320, 90], [320, 175], [450, 175]], [[470, 20], [470, 95], [545, 95], [545, -10], [545, 180]] ]; const colors = ["#ff0000", "#0000ff", "#008000", "#800080"]; //основные цвета для градиента const colors2 = ["#800080", "#004080", "#404040", "#c00040"]; //промежуточные цвета для анимации ctx.lineWidth = 30; //толщина линии ctx.lineCap = 'round'; let offset = 0, delta = 0.01; function animateGradient() { points.forEach((line, index) => { let len = line.length - 1; if (line[0][0] == line[len][0] && line[0][1] == line[len][1]) len = Math.floor(len / 2); //конечная точка градиента зависит от того, замкнут ли контур const gradient = ctx.createLinearGradient(line[0][0], line[0][1], line[len][0], line[len][1]); gradient.addColorStop(0, colors[index]); gradient.addColorStop(offset, colors2[index]); gradient.addColorStop(1, colors[(index + 1)%colors.length]); ctx.strokeStyle = gradient; ctx.beginPath(); ctx.moveTo(line[0][0], line[0][1]); for (let i = 1; i < line.length - 1; i++) { const cpX = (line[i][0] + line[i + 1][0]) / 2; const cpY = (line[i][1] + line[i + 1][1]) / 2; ctx.quadraticCurveTo(line[i][0], line[i][1], cpX, cpY); } ctx.lineTo(line[line.length - 1][0], line[line.length - 1][1]); ctx.stroke(); }); offset += delta; if (offset >= 1) { offset = 1; delta = -0.01; } else if (offset <= 0) { offset = 0; delta = 0.01; } setTimeout(() => { requestAnimationFrame(animateGradient); }, 1000/60); } animateGradient(); </script> <noscript> <p>Включите Javascript в браузере для работы приложения.</p> </noscript>
13.06.2024, 12:47 [276 просмотров]