Снова приближается День холостяков, и это время года для меня самое депрессивное. Кошелек тоньше и руки все более и более сохнут, хоть и окреп, голова похолодела. Надо что-то делать в этом году!
Недавно я услышал от богини, что она хочет влюбиться, ✧(≖ ◡ ≖) Эй, ты не должен упустить эту возможность и дать ей другое признание.
Как фронтенд осадному льву, который целыми днями занимается визуализацией, первое, что приходит на ум, — это различные частицы, которые часто проигрываются. Итак, давайте вместе поиграем с этой системой частиц.демонстрационный адрес
Составление текста с частицами
Прежде всего, давайте подумаем, как составить из ряда частиц высказывание?
Принцип реализации на самом деле очень прост, в 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);
Мы можем добавить ребенка к телу, чтобы увидеть
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 上
}
Таким образом, наши основные функции были выполнены.
Можешь дать мне больше силы?
Говорят, что система частиц хороша, теперь это просто простая отрисовка 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),
};
}
}
Готово!
Поскольку это 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
};
}
}
Идеально 😝.
Затем мы также можем использовать drawImage для рисования изображений вместо дуги для рисования точек.
так далее! ! Предыдущий эффект всегда чувствует, что что-то не так, как будто есть какие-то карты.
Советы по оптимизации
- Слоистый. Если вам нужно добавить какой-либо другой контент в Canvas, вы можете рассмотреть возможность использования нескольких Canvas для этого.
- Уменьшите настройки свойства. В том числе lineWidth, fillStyle и т. д. Контекст Canvas — это очень сложный объект, и он потребляет много ресурсов, когда вы устанавливаете некоторые из его свойств. Методы рисования, такие как дуга, также сокращены.
- Закадровый рисунок. Как рисовать точки без дуги. Вышеупомянутая задержка фактически вызывает fillStyle и arc каждый раз, когда точка рисуется. Но когда мы рисуем изображение drawImage, производительность, очевидно, намного лучше. В дополнение к рисованию изображений напрямую, drawImage также может рисовать другой Canvas, поэтому мы можем заранее рисовать эти точки на Canvas, которого нет на экране.
- Чтобы уменьшить количество вычислений js и избежать блокировки процесса, вы можете использовать веб-воркеры. Конечно, в наших текущих расчетах это вообще не используется.
Я использовал 3000 частиц и сравнил частоту кадров до и после использования внеэкранного рисования.
Суммировать
Единственное, что вас сейчас ограничивает, это ваше воображение. Идем побеждать босса и побеждать богиню вместе!
Этот Double Eleven избавится от бедности и одиночества без выпадения волос!
Ну, давайте не будем говорить об этом, богиня позвонила мне, чтобы починить компьютер.
Ссылаться на
-
y=sqrt(abs(x))-sqrt(cos(x))*cos(40x)
Статья может быть воспроизведена по желанию, но просьба сохранитьОригинальная ссылка. Добро пожаловать!ES2049 Studio, отправьте свое резюме на caijun.hcj(at)alibaba-inc.com.