Моделирование кругового движения в холсте HTML 5

Следующий код создает круг в HTML 5 Canvas с помощью jQuery:

Код:

//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");

DrawCircle(75, 75, 20);

//draw a circle
function DrawCircle(x, y, radius)
{
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI*2, true); 
    ctx.fillStyle = 'transparent';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#003300';
    ctx.stroke();
    ctx.closePath();
    ctx.fill();
}

Я пытаюсь имитировать любой из следующих типов кругов:

examples

Я исследовал и нашел эту статью, но не смог ее применить.

Я бы хотел, чтобы круг был нарисован, а не просто появился.

Есть лучший способ сделать это? Я чувствую, что там будет много математики :)

PS Мне нравится простота PaperJs, может быть, это был бы самый простой подход, используя упрощенные пути?

Ответы

Ответ 1

Здесь уже есть хорошие решения. Я хотел бы добавить варианты того, что уже было представлено - вариантов некоторых тригонометрии не так много, если вы хотите имитировать рисованные круги.

Сначала я бы рекомендовал записать реальный ручной кружок. Вы можете записывать точки, а также timeStamp и воспроизводить точный чертеж в любое время позже. Вы могли бы объединить это с алгоритмом сглаживания линии.

Это решение создает такие круги, как:

Snapshot

Вы можете изменить цвет, толщину и т.д., Установив strokeStyle, lineWidth и т.д., Как обычно.

Чтобы нарисовать круг, просто позвоните:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);

(callback предоставляется, поскольку анимация делает функцию асинхронной).

Код разделяется на два сегмента:

  1. Создать точки
  2. Анимировать точки

Инициализация:

function handDrawCircle(ctx, cx, cy, r, rounds, callback) {

    /// rounds is optional, defaults to 3 rounds    
    rounds = rounds ? rounds : 3;

    var x, y,                                      /// the calced point
        tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
        dx = Math.random() * tol * 0.75,           /// "bouncer" values
        dy = Math.random() * tol * 0.75,
        ix = (Math.random() - 1) * (r * 0.0044),   /// speed /incremental
        iy = (Math.random() - 1) * (r * 0.0033),
        rx = r + Math.random() * tol,              /// radius X 
        ry = (r + Math.random() * tol) * 0.8,      /// radius Y
        a = 0,                                     /// angle
        ad = 3,                                    /// angle delta (resolution)
        i = 0,                                     /// counter
        start = Math.random() + 50,                /// random delta start
        tot = 360 * rounds + Math.random() * 50 - 100,  /// end angle
        points = [],                               /// the points array
        deg2rad = Math.PI / 180;                   /// degrees to radians

В основном цикле мы не скатываемся случайным образом, а увеличиваем со случайным значением, а затем линейно увеличиваем его с этим значением, отбрасываем его, если мы находимся на границах (допуск).

for (; i < tot; i += ad) {
    dx += ix;
    dy += iy;

    if (dx < -tol || dx > tol) ix = -ix;
    if (dy < -tol || dy > tol) iy = -iy;

    x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
    y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);

    points.push(x, y);
}

И в последнем сегменте мы просто визуализируем то, что у нас есть.

Скорость определяется da (угол дельта) на предыдущем шаге:

    i = 2;

    /// start line    
    ctx.beginPath();
    ctx.moveTo(points[0], points[1]);

    /// call loop
    draw();

    function draw() {

        ctx.lineTo(points[i], points[i + 1]);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(points[i], points[i + 1]);

        i += 2;

        if (i < points.length) {
            requestAnimationFrame(draw);
        } else {
            if (typeof callback === 'function')
                callback();
        }
    }
}

(см. комментарии ниже в разделе обновления)

Совет. Чтобы получить более реалистичный штрих, вы можете уменьшить globalAlpha до, например, 0.7.

Тем не менее, для правильной работы вам нужно сначала нарисовать сплошное изображение на экране, а затем разбить этот экранный холст на основной холст (который имеет набор globalAlpha) для каждого кадра, иначе штрихи будут перекрываться между каждой точкой ( который выглядит не очень хорошо).

Для квадратов вы можете использовать тот же подход, что и для круга, но вместо использования радиуса и угла вы применяете вариации к линии. Сдвиньте дельты, чтобы линия не прямая.

Я немного изменил эти ценности, но не стесняйтесь настраивать их больше, чтобы получить лучший результат.

Чтобы немного накрутить кружок, вы можете немного немного повернуть холст:

rotate = Math.random() * 0.5;

ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);

и когда петля заканчивается:

if (i < points.length) {
    requestAnimationFrame(draw);
} else {
   ctx.restore();
}

(включены в демонстрацию, указанную выше).

Круг будет выглядеть следующим образом:

Snapshot tilted

Обновить

Чтобы справиться с упомянутыми проблемами (поля комментариев слишком маленькие :-)): на самом деле это немного сложнее для создания анимированных строк, особенно в таком случае, где вы круговое движение, а также случайная граница.

Ссылка точка комментария 1: допуск тесно связан с радиусом, поскольку он определяет максимальное колебание. Мы можем изменить код, чтобы принять допуск (и ix/iy поскольку они определяют, как "быстро" он будет меняться) на основе радиуса. Это то, что я подразумеваю под настройкой, чтобы найти это значение/сладкое пятно, которое хорошо работает со всеми размерами. Чем меньше круг, тем меньше вариации. Необязательно укажите эти значения в качестве аргументов функции.

Пункт 2: поскольку мы анимируем круг, функция становится асинхронной. Если мы рисуем два круга сразу после друг друга, они испортят холст, поскольку новые точки добавляются к пути из обоих кругов, которые затем поглаживаются перекрестно.

Мы можем обойти это, предоставив механизм обратного вызова:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);

а затем, когда анимация закончилась:

if (i < points.length) {
    requestAnimationFrame(draw);

} else {
    ctx.restore();
    if (typeof callback === 'function')
        callback();  /// call next function
}

Другие проблемы, с которыми вы столкнулись с кодом как есть (помните, что код подразумевается как пример, а не полное решение :-)) с толстыми строками:

Когда мы рисуем сегмент по сегменту, отдельно холст не знает, как вычислить угол стыка линии по отношению к предыдущему сегменту. Это часть концепции пути. Когда вы поглаживаете путь с несколькими сегментами, холст знает, по какому углу будет прикладом (конец линии). Таким образом, здесь мы либо рисуем линию от начала до текущей точки, либо делаем четкие промежутки между ними или только небольшие значения lineWidth.

Когда мы используем clearRect (который сделает линию гладкой, а не "дряблой", поскольку, когда мы не используем прозрачную между ними, а просто делаем сверху), нам нужно будет рассмотреть возможность создания верхнего холста для анимации с анимацией и при анимации заканчивается, мы рисуем результат на основном холсте.

Теперь мы начинаем видеть часть "сложности". Это, конечно, потому, что холст "низкоуровневый" в том смысле, что нам нужно обеспечить всю логику для всего. Мы в основном строим системы каждый раз, когда делаем что-то большее с холстом, чем просто рисуем простые фигуры и изображения (но это также дает большую гибкость).

Я рассматриваю пару проблем в этой обновленной скрипке, но вам все равно нужно немного подкорректировать (обновлено с верхним холстом, обратным вызовом, динамическим допуском).

Ответ 2

Вот некоторые основы, которые я создал для этого ответа:

http://jsfiddle.net/Exceeder/TPDmn/

В принципе, когда вы рисуете круг, вам нужно учитывать недостатки рук. Итак, в следующем коде:

var img = new Image();
img.src="data:image/png;base64,...";

var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
  ctx.drawImage(img, x, y);
}

for (var i=0; i<500; i++) {
    var radiusError = +10 - i/20;
    var d = 2*Math.PI/360 * i;
    draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}

Обратите внимание, как изменяется вертикальный радиус, когда угол (и положение) растет. Вы можете играть с этой скрипкой, пока не почувствуете, какой компонент что-то делает. Например, было бы целесообразно ввести еще один компонент в radiusError, который эмулирует "неустойчивую" руку, медленно меняя ее мои случайные суммы.

Существует много разных способов сделать это. Я выбираю триггерные функции для простоты моделирования, поскольку скорость здесь не является фактором.

Обновить:

Это, например, сделает его менее совершенным:

var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);

Очевидно, что центр круга находится в точке (200,200), так как формула для рисования круга (скорее, многоточия с вертикальным радиусом RY и горизонтальным радиусом RX) с тригонометрическими функциями равна

x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )

Ответ 3

Кажется, у вашей задачи 3 требования:

  1. Ручная нарисованная форма.
  2. "Органический", а не "сверхточный" удар.
  3. Выявление круга пошагово, а не для всех.

Чтобы начать работу, ознакомьтесь с этой замечательной демонстрацией на целевой аудитории от Andrew Trice.

Этот удивительный круг рисован мной (теперь вы можете смеяться...!)

My amazing circle created with Andrew's technique

Эндрю demo делает шаги 1 и 2 ваших требований.

Это позволяет вам нарисовать круг (или любую форму), используя органический "эффект кисти" вместо обычных сверхточных линий, обычно используемых в холсте.

Он достигает "эффекта кисти" путем повторного рисования кисти между пунктами руки

Вот демо:

http://tricedesigns.com/portfolio/sketch/brush.html#

И код доступен на GitHub:

https://github.com/triceam/HTML5-Canvas-Brush-Sketch

Демо-версия Andrew Trices рисует и забывает строки, составляющие ваш круг.

Ваша задача состояла бы в том, чтобы повлиять на ваше третье требование (запоминание штрихов):

  • Рука нарисуйте собственный круг,
  • Сохраните каждый сегмент линии, который составляет ваш круг в массиве,
  • "Играйте" эти сегменты с использованием стилизованной кисти Эндрюса.

Результаты: рисованный и стилизованный круг, который появляется постепенно, а не сразу.

У вас интересный проект... Если вы чувствуете себя щедрым, поделитесь своими результатами!

Ответ 4

Смотрите демо-версию здесь. Также доступно как сущность.

<div id="container">
    <svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>

#container {
  width:500px;
  height:300px;
}
path.ln {
  stroke-width: 3px;
  stroke: #666;
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  -webkit-animation: dash 5s ease-in forwards;
  -moz-animation:dash 5s ease-in forwards;
  -o-animation:dash 5s ease-in forwards;
  animation:dash 5s ease-in forwards;
}

@keyframes dash {
  to { stroke-dashoffset: 0; }
}

function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {

    var c = 0.551915024494;
    var atan = Math.atan(c)
    var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
    var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
    var path = 'M';

    path += [r * Math.sin(el), r * Math.cos(el)];
    path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];

    for (var i = 0; i < 4; i++) {
        el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
        r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
        path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
        path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
    }

    return path;
}

function cX(λ_min, λ_max, el_min, el_max) {
    var el = (el_min + Math.random()*(el_max - el_min));
    return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}

function canvasArea() {
    var width = Math.floor((Math.random() * 500) + 450);
  var height = Math.floor((Math.random() * 300) + 250);
    $('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));

setTimeout(function() { location = '' } ,5000)

Ответ 5

Будучи столкнувшись с подобными задачами, я создал мультяшную библиотеку рисования JS для SVG или холста HTML5. Он работает как плагин для Raphael.js, D3.js или SVG.js или как lib для Canvas. Он называется comic.js и может быть найден на github. Среди других форм он может рисовать похожие круги, которые вы просили. Он основан на статье, которую вы упоминаете.

Это то, что он может произвести:

comic.js screenshot