Крутые частицы признались, и Double Eleven в одиночестве!

внешний интерфейс JavaScript Алибаба Canvas
Крутые частицы признались, и Double Eleven в одиночестве!

Снова приближается День холостяков, и это время года для меня самое депрессивное. Кошелек тоньше и руки все более и более сохнут, хоть и окреп, голова похолодела. Надо что-то делать в этом году!

fxxking things

Недавно я услышал от богини, что она хочет влюбиться, ✧(≖ ◡ ≖) Эй, ты не должен упустить эту возможность и дать ей другое признание.

我老婆

Как фронтенд осадному льву, который целыми днями занимается визуализацией, первое, что приходит на ум, — это различные частицы, которые часто проигрываются. Итак, давайте вместе поиграем с этой системой частиц.демонстрационный адрес

Составление текста с частицами

Прежде всего, давайте подумаем, как составить из ряда частиц высказывание?

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

Первым шагом является вычисление соответствующего размера и местоположения текста с помощью MeasureText.

// 创建一个跟画布等比例的 canvas
const width = 100;
const height = ~~(width * this.height / this.width); // this.width , this.height 说整个画布的尺寸
const offscreenCanvas = document.createElement('canvas');
const offscreenCanvasCtx = offscreenCanvas.getContext('2d');
offscreenCanvas.setAttribute('width', width);
offscreenCanvas.setAttribute('height', height);

// 在这离屏 canvas 中将我们想要的文字 textAll 绘制出来后,再计算它合适的尺寸
offscreenCanvasCtx.fillStyle = '#000';
offscreenCanvasCtx.font = 'bold 10px Arial';
const measure = offscreenCanvasCtx.measureText(textAll); // 测量文字,用来获取宽度
const size = 0.8;
// 宽高分别达到屏幕0.8时的size
const fSize = Math.min(height * size * 10 / lineHeight, width * size * 10 / measure.width);  // 10像素字体行高 lineHeight=7 magic
offscreenCanvasCtx.font = `bold ${fSize}px Arial`;

// 根据计算后的字体大小,在将文字摆放到适合的位置,文字的坐标起始位置在左下方
const measureResize = offscreenCanvasCtx.measureText(textAll);
// 文字起始位置在左下方
let left = (width - measureResize.width) / 2;
const bottom = (height + fSize / 10 * lineHeight) / 2;
offscreenCanvasCtx.fillText(textAll, left, bottom);

Мы можем добавить ребенка к телу, чтобы увидеть

textAll

OK. Внимание одноклассники, я начну деформировать [толкать очки].

Пиксельные данные, полученные с помощью getImageData, представляют собой Uint8ClampedArray (массив со значением от 0 до 255), а группа из 4 чисел соответствует значению RGB A пикселя. Нам нужно только решить, что i * 4 + 3 не равно 0, чтобы получить необходимые данные о форме шрифта.

// texts 所有的单词分别获取 data ,上文的 textAll 是 texts 加一起
Object.values(texts).forEach(item => {
    offscreenCanvasCtx.clearRect(0, 0, width, height);
    offscreenCanvasCtx.fillText(item.text, left, bottom);
    left += offscreenCanvasCtx.measureText(item.text).width;
    const data = offscreenCanvasCtx.getImageData(0, 0, width, height);
    const points = [];
	// 判断第 i * 4 + 3 位是否为0,获得相对的 x,y 坐标(使用时需乘画布的实际长宽, y 坐标也需要取反向)
    for (let i = 0, max = data.width * data.height; i < max; i++) {
        if (data.data[i * 4 + 3]) {
            points.push({
                x: (i % data.width) / data.width,
                y: (i / data.width) / data.height
            });
        }
    }
	// 保存到一个对象,用于后面的绘制
    geometry.push({
        color: item.hsla,
        points
    });
})

Создавайте сцены, рисуйте графику

Метод получения текста и графики завершен, теперь мы можем выводить содержимое целиком. Давайте определим простой формат скрипта.

// hsla 格式方便以后做色彩变化的扩展
const color1 = {h:197,s:'100%',l:'50%',a:'80%'};
const color2 = {h:197,s:'100%',l:'50%',a:'80%'};
// lifeTime 祯数
const Actions = [
    {lifeTime:60,text:[{text:3,hsla:color1}]},
    {lifeTime:60,text:[{text:2,hsla:color1}]},
    {lifeTime:60,text:[{text:1,hsla:color1}]},
    {lifeTime:120,text:[
        {text:'I',hsla:color1},
        {text:'❤️',hsla:color2},
        {text:'Y',hsla:color1},
        {text:'O',hsla:color1},
        {text:'U',hsla:color1}
    ]},
];

В соответствии с предустановленным скриптом анализируется график каждой сцены и добавляется галочка, чтобы судить о том, является ли timeTime переключением на следующий график для перерисовки графика.

function draw() {
    this.tick++;
    if (this.tick >= this.actions[this.actionIndex].lifeTime) {
        this.nextAction();
    }
    this.clear();
    this.renderParticles(); // 绘制点
    this.raf = requestAnimationFrame(this.draw);
}

function nextAction() {
    ....//切换场景 balabala..
    this.setParticle(); // 随机将点设置到之前得到的 action.geometry.points 上
}

1

Таким образом, наши основные функции были выполнены.

Можешь дать мне больше силы?

Говорят, что система частиц хороша, теперь это просто простая отрисовка context.arc. Затем добавим систему частиц.

class PARTICLE {
    // x,y,z 为当前的坐标,vx,vy,vz 则是3个方向的速度
    constructor(center) {
        this.center = center;
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.vx = 0;
        this.vy = 0;
        this.vz = 0;
    }
    // 设置这些粒子需要运动到的终点(下一个位置)
    setAxis(axis) {
        this.nextX = axis.x;
        this.nextY = axis.y;
        this.nextZ = axis.z;
        this.color = axis.color;
    }
    step() {
        // 弹力模型 距离目标越远速度越快
        this.vx += (this.nextX - this.x) * SPRING;
        this.vy += (this.nextY - this.y) * SPRING;
        this.vz += (this.nextZ - this.z) * SPRING;
		// 摩擦系数 让粒子可以趋向稳定
        this.vx *= FRICTION;
        this.vy *= FRICTION;
        this.vz *= FRICTION;
		
        this.x += this.vx;
        this.y += this.vy;
        this.z += this.vz;
    }
    getAxis2D() {
        this.step();
        // 3D 坐标下的 2D 偏移,暂且只考虑位置,不考虑大小变化
        const scale = FOCUS_POSITION / (FOCUS_POSITION + this.z);
        return {
            x: this.center.x + (this.x * scale),
            y: this.center.y - (this.y * scale),
        };
    }
}

2

Готово!

Поскольку это 3D-частицы, на самом деле есть статьи, которые можно написать на эту тему, и студенты могут использовать свое воображение, чтобы сделать что-то более крутое.

что еще весело

Вышеупомянутое предназначено для размещения частиц в тексте. Тогда, конечно, мы также можем напрямую написать формулу для создания формы.

// Actions 中用 func 代替 texts
{
    lifeTime: 100,
    func: (radius) => {
        const i = Math.random() * 1200;
        let x = (i - 1200 / 2) / 300;
        let y = Math.sqrt(Math.abs(x)) - Math.sqrt(Math.cos(x)) * Math.cos(30 * x);
        return {
            x: x * radius / 2,
            y: y * radius / 2,
            z: ~~(Math.random() * 30),
            color: color3
        };
    }
}

Тогда используйте метод трансформации формы текста прямо сейчас.

{
    lifeTime: Infinity,
        func: (width, height) => {
            if(!points.length){
                const img = document.getElementById("tulip");
                const offscreenCanvas = document.createElement('canvas');
                const offscreenCanvasCtx = offscreenCanvas.getContext('2d');
                const imgWidth = 200;
                const imgHeight = 200;
                offscreenCanvas.setAttribute('width', imgWidth);
                offscreenCanvas.setAttribute('height', imgHeight);
                offscreenCanvasCtx.drawImage(img, 0, 0, imgWidth, imgHeight);
                let imgData = offscreenCanvasCtx.getImageData(0, 0, imgWidth, imgHeight);
                for (let i = 0, max = imgData.width * imgData.height; i < max; i++) {
                    if (imgData.data[i * 4 + 3]) {
                        points.push({
                            x: (i % imgData.width) / imgData.width,
                            y: (i / imgData.width) / imgData.height
                        });
                    }
                }
            }

            const p = points[~~(Math.random() * points.length)]
            const radius = Math.min(width * 0.8, height * 0.8);
            return {
                x: p.x * radius - radius / 2,
                y: (1 - p.y) * radius - radius / 2,
                z: ~~(Math.random() * 30),
                color: color3
            };
        }
}

3

Идеально 😝.

Затем мы также можем использовать drawImage для рисования изображений вместо дуги для рисования точек.

так далее! ! Предыдущий эффект всегда чувствует, что что-то не так, как будто есть какие-то карты.

稳得一笔

Советы по оптимизации

  1. Слоистый. Если вам нужно добавить какой-либо другой контент в Canvas, вы можете рассмотреть возможность использования нескольких Canvas для этого.
  2. Уменьшите настройки свойства. В том числе lineWidth, fillStyle и т. д. Контекст Canvas — это очень сложный объект, и он потребляет много ресурсов, когда вы устанавливаете некоторые из его свойств. Методы рисования, такие как дуга, также сокращены.
  3. Закадровый рисунок. Как рисовать точки без дуги. Вышеупомянутая задержка фактически вызывает fillStyle и arc каждый раз, когда точка рисуется. Но когда мы рисуем изображение drawImage, производительность, очевидно, намного лучше. В дополнение к рисованию изображений напрямую, drawImage также может рисовать другой Canvas, поэтому мы можем заранее рисовать эти точки на Canvas, которого нет на экране.
  4. Чтобы уменьшить количество вычислений js и избежать блокировки процесса, вы можете использовать веб-воркеры. Конечно, в наших текущих расчетах это вообще не используется.

Я использовал 3000 частиц и сравнил частоту кадров до и после использования внеэкранного рисования.

方不方
圆了

Суммировать

Единственное, что вас сейчас ограничивает, это ваше воображение. Идем побеждать босса и побеждать богиню вместе!

Этот Double Eleven избавится от бедности и одиночества без выпадения волос!

Ну, давайте не будем говорить об этом, богиня позвонила мне, чтобы починить компьютер.

Ссылаться на

Статья может быть воспроизведена по желанию, но просьба сохранитьОригинальная ссылка. Добро пожаловать!ES2049 Studio, отправьте свое резюме на caijun.hcj(at)alibaba-inc.com.