В прямом эфире есть очень важное взаимодействие: лайки.
Чтобы усилить атмосферу помещения прямого эфира по сравнению с обычным видео или текстовым контентом, обычно к лайкам в прямых эфирах предъявляются два особых требования:
- Неограниченное количество лайков, помогайте пользователям безумно любить
- Все сумасшедшие лайки в комнате прямой трансляции должны быть анимированы во всех пользовательских интерфейсах.
Давайте сначала посмотрим на рендеры:
Из визуализаций мы также видим несколько важных сведений:
- Как и анимационные картинки различаются по размеру, и траектория движения тоже случайна
- Как и в анимации, изображения сначала увеличиваются, а затем перемещаются с одинаковой скоростью.
- Когда он достигает вершины, он постепенно исчезает.
- При получении большого количества подобных запросов похожие анимации не группируются вместе и продолжают появляться упорядоченным образом.
Так как же выполнить эти требования? Представлены следующие два метода реализации (полная демонстрация прилагается внизу):
Реализация CSS3
Используя CSS3 для достижения анимации, очевидно, мы думаем об использовании анимации.
Для начала рассмотрим анимационный комбинированный способ написания, конкретный смысл объяснять не буду, если надо, то разберетесь сами.
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
Давайте начнем делать это шаг за шагом.
Шаг 1: Исправьте область и задайте основной стиль
Сначала подготовим подобную анимационную картинку:
Взгляните на структуру HTML. Внешняя структура фиксирует положение всей области анимации отображения. Вот область div шириной 100px и высотой 200px.
<div class="praise_bubble">
<div class="bubble b1 bl1"></div>
</div>
.praise_bubble{
width:100px;
height:200px;
position:relative;
background-color:#f4f4f4;
}
.bubble{
position: absolute;
left:50%;
bottom:0;
}
Шаг 2: Двигайтесь
Используя анимацию кадра анимации, определите последовательность кадров bubble_y.
.bl1{
animation:bubble_y 4s linear 1 forwards ;
}
@keyframes bubble_y {
0% {
margin-bottom:0;
}
100% {
margin-bottom:200px;
}
}
Здесь время работы установлено на 4 с;
Линейное движение принимается линейным, и другие кривые, такие как легкость, конечно, могут использоваться при необходимости;
Каждая похожая анимация запускается только один раз;
Анимация требуется только для переадресации вперед.
Шаг 3: Добавьте затухание
Чтобы уменьшить эффект, используйте непрозрачность. Здесь мы исправляем затухание в последней 1/4. Измените пузырь_у:
@keyframes bubble_y {
0% {
margin-bottom:0;
}
75%{
opacity:1;
}
100% {
margin-bottom:200px;
opacity:0;
}
}
Шаг 4: Добавьте эффект масштабирования анимации
В первый короткий промежуток времени картинка меняется от мелкой к крупной.
Поэтому мы добавили анимацию: bubble_big_1.
Здесь исходное изображение увеличено с 0,3x до 1x. Обратите внимание на время выполнения здесь, например, в приведенной выше настройке, общее время от начала до конца анимации составляет 4 с, тогда это время масштабирования можно установить по мере необходимости, например, 0,5 с.
.bl1{
animation:bubble_big 0.5s linear 1 forwards;
}
@keyframes bubble_big_1 {
0% {
transform: scale(.3);
}
100% {
transform: scale(1);
}
}
Шаг 5: Установите смещение
Сначала мы определяем анимацию кадра: bubble_1 для выполнения смещения. Изображение начинает увеличиваться. Здесь не задано смещение, центральная точка остается неизменной.
После пробежки до 25% * 4 = 1с, то есть через 1с, при смещении влево на -8px, на 2с, смещается вправо на 8px, а при 3с, сдвигается на вправо на 15 пикселей, и, наконец, он сдвинут вправо на 15 пикселей.
Как вы можете себе представить, это классическая траектория поворота влево-вправо, эффект поворота кривой «влево-вправо-влево-вправо».
@keyframes bubble_1 {
0% {
}
25% {
margin-left:-8px;
}
50% {
margin-left:8px
}
75% {
margin-left:-15px
}
100% {
margin-left:15px
}
}
Схема эффекта выглядит следующим образом:
Шаг 6: Завершите стиль анимации
Здесь предустановлена кривая бегущей дорожки, а также предустановлен стиль качания влево и вправо.
Например, для левой и правой анимационной дорожки смещения bubble_1 мы можем изменить значение смещения, чтобы получить различные дорожки кривой.
Шаг 7: операция JS случайным образом увеличивает стиль узла
Предоставьте метод добавления лайков, случайным образом комбинируйте стили лайков, а затем визуализируйте их в узле.
let praiseBubble = document.getElementById("praise_bubble");
let last = 0;
function addPraise() {
const b =Math.floor(Math.random() * 6) + 1;
const bl =Math.floor(Math.random() * 11) + 1; // bl1~bl11
let d = document.createElement("div");
d.className = `bubble b${b} bl${bl}`;
d.dataset.t = String(Date.now());
praiseBubble.appendChild(d);
}
setInterval(() => {
addPraise();
},300)
При использовании CSS для реализации лайков обычно нужно обращать внимание на настройку случайной задержки пузырька, например:
.bl2{
animation:bubble_2 $bubble_time linear .4s 1 forwards,bubble_big_2 $bubble_scale linear .4s 1 forwards,bubble_y $bubble_time linear .4s 1 forwards;
}
Если это случайно для bl2, то запускается с задержкой 0,4 с, bl3 с задержкой 0,6 с...
Если он обновляется на узлах пакетами и не задана задержка, он будет отображаться группами. Случайный стиль «bl», случайная задержка, а затем появляются в пакетном режиме, автоматически отображаются в шахматном порядке. Конечно, нам также нужно увеличить анимацию текущих лайков руководства пользователя, которая не требует задержки.
Кроме того, возможно, что другие одновременно отправили лайки 40. Бизнес-требование обычно состоит в том, чтобы надеяться, что эти 40 пузырьков лайков могут появляться последовательно, создавая непрерывную атмосферу лайков, в противном случае они будут отображаться вместе, когда количество лайков посты большие.
Затем нам также нужно разбить количество лайков на пакеты.Например, время для лайка ($bubble_time) составляет 4 секунды, тогда в течение 4 секунд сколько лайков вы хотите, чтобы появилось одновременно? Например, если 10, то для 40 лайков потребуется 4 отрисовки пачками.
window.requestAnimationFrame(() => {
// 继续循环处理批次
render();
});
Также необходимо вручную очищать узлы. Чтобы предотвратить проблемы с производительностью, вызванные слишком большим количеством узлов. Ниже представлен полный рендеринг.
Реализация рисования холста
Это легко понять, просто рисуйте анимацию прямо на канве, если вы не знаете канвас, вы можете изучить его позже.
Шаг 1: Инициализация
Создайте новый тег холста в элементе страницы, чтобы инициализировать холст.
Атрибуты ширины и высоты могут быть установлены на холсте, а ширина и высота также могут быть установлены в атрибуте стиля.
- Ширина и высота стиля на холсте — это высота и ширина холста, отображаемого в браузере, то есть фактическая ширина и высота на странице.
- Ширина и высота тега холста — это фактическая ширина и высота холста.
<canvas id="thumsCanvas" width="200" height="400" style="width:100px;height:200px"></canvas>
На странице есть холст холст шириной 200 и высотой 400, а затем весь холст отображается в области страницы шириной 100 и высотой 200. холст Содержимое холста уменьшается и отображается на странице.
Определите подобный класс ThumbsUpAni, конструктор должен читать холст и сохранять значения ширины и высоты.
class ThumbsUpAni{
constructor(){
const canvas = document.getElementById('thumsCanvas');
this.context = canvas.getContext('2d')!;
this.width = canvas.width;
this.height = canvas.height;
}
}
Шаг 2. Заранее загрузите ресурсы изображений
Предварительно загрузите похожее изображение, которое необходимо отрендерить случайным образом, чтобы получить ширину и высоту изображения. Если загрузка не удалась, случайное изображение не будет отображаться. Нечего сказать, легко понять.
loadImages(){
const images = [
'jfs/t1/93992/8/9049/4680/5e0aea04Ec9dd2be8/608efd890fd61486.png',
'jfs/t1/108305/14/2849/4908/5e0aea04Efb54912c/bfa59f27e654e29c.png',
'jfs/t1/98805/29/8975/5106/5e0aea05Ed970e2b4/98803f8ad07147b9.png',
'jfs/t1/94291/26/9105/4344/5e0aea05Ed64b9187/5165fdf5621d5bbf.png',
'jfs/t1/102753/34/8504/5522/5e0aea05E0b9ef0b4/74a73178e31bd021.png',
'jfs/t1/102954/26/9241/5069/5e0aea05E7dde8bda/720fcec8bc5be9d4.png'
];
const promiseAll = [] as Array<Promise<any>>;
images.forEach((src) => {
const p = new Promise(function (resolve) {
const img = new Image;
img.onerror = img.onload = resolve.bind(null, img);
img.src = 'https://img12.360buyimg.com/img/' + src;
});
promiseAll.push(p);
});
Promise.all(promiseAll).then((imgsList) => {
this.imgsList = imgsList.filter((d) => {
if (d && d.width > 0) return true;
return false;
});
if (this.imgsList.length == 0) {
logger.error('imgsList load all error');
return;
}
})
}
Шаг 2: Создайте объект рендеринга
Визуализируйте изображение в реальном времени, превратив его в связную анимацию, и самое главное: сгенерируйте кривую дорожку. Эта кривая траектория должна быть гладкой однородной кривой. Если сгенерированная траектория кривой не гладкая, эффект будет слишком резким, например, предыдущая 10px, а следующая -10px, очевидно, анимация мерцает слева направо.
Идеальная траектория состоит в том, что последняя позиция составляет 10 пикселей, следующая — 9 пикселей, а затем сглажена до -10 пикселей, поэтому точки координат совпадают, и кажется, что анимация выполняется плавно.
Случайное плавное смещение по оси X
Если вы хотите получить плавную кривую, вы можете использовать функцию синуса ( Math.sin ), с которой мы все знакомы, для получения однородной кривой.
Посмотрите на синусоиду на рисунке ниже:
Это кривая от Math.sin(0) до Math.sin(9) Это гладкая кривая от положительного к отрицательному, а затем от отрицательного к положительному, что полностью соответствует нашим потребностям, поэтому нам нужно сгенерировать случайное значение коэффициента для рандомизации амплитуды колебаний.
const angle = getRandom(2, 10);
let ratio = getRandom(10,30)*((getRandom(0, 1) ? 1 : -1));
const getTranslateX = (diffTime) => {
if (diffTime < this.scaleTime) {// 放大期间,不进行摇摆偏移
return basicX;
} else {
return basicX + ratio*Math.sin(angle*(diffTime - this.scaleTime));
}
};
scaleTime — это время, которое требуется для увеличения от начала до конечного размера.Здесь мы устанавливаем 0,1, что составляет 10% времени до общего времени выполнения.Как изображения постепенно увеличиваются.
diffTime — это только то, сколько времени прошло от начала анимации до текущего времени в процентах. Фактическое значение постепенно увеличивается от 0 --> 1. diffTime - scaleTime = 0 ~ 0,9, когда diffTime равно 0,4, это означает, что было запущено 40% времени.
Поскольку кривая от Math.sin(0) до Math.sin(0.9) представляет собой почти прямую линию, она не соответствует эффекту колебания. ), так что здесь мы устанавливаем минимальный угол на 2.
Это устанавливает угол коэффициента угла на максимальное значение 10 , проходящее два пика снизу вверх.
Конечно, если расстояние бега больше, мы можем увеличить значение угла, например, оно станет 3 пика (если время короткое и появятся три пика, он будет работать слишком быстро и будет мерцание). Как показано ниже:
Смещение оси Y
Это легко понять, start diffTime равно 0, поэтому запустите offset from this.height --> image.height/2. т.е. с самого низа, бегом влево вверх, по сути мы будем фейдить скрытое вверху.
const getTranslateY = (diffTime) => {
return image.height / 2 + (this.height - image.height / 2) * (1-diffTime);
};
приблизить
Когда время выполнения diffTime меньше, чем заданное значение scaleTime, масштаб увеличивается по мере пропорционального увеличения времени. Если установленный порог времени превышен, возвращается окончательный размер.
const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)];
const getScale = (diffTime) => {
if (diffTime < this.scaleTime) {
return +((diffTime/ this.scaleTime).toFixed(2)) * basicScale;
} else {
return basicScale;
}
};
исчезать
Это то же самое, что и логика увеличения, за исключением того, что исчезновение вступает в силу в конце прогона.
const fadeOutStage = getRandom(14, 18) / 100;
const getAlpha = (diffTime) => {
let left = 1 - +diffTime;
if (left > fadeOutStage) {
return 1;
} else {
return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2);
}
};
рисовать в реальном времени
После того, как объект рисования создан, его можно отрисовывать в реальном времени.По полученным выше значениям «смещение», «увеличение» и «затухание» положение понравившейся картинки можно отрисовать в реальном времени.
Каждый цикл выполнения должен перерисовывать все позиции анимационных изображений на холсте и, наконец, формировать эффект движения всех одинаковых изображений.
createRender(){
return (diffTime) => {
// 差值满了,即结束了 0 ---》 1
if(diffTime>=1) return true;
context.save();
const scale = getScale(diffTime);
const translateX = getTranslateX(diffTime);
const translateY = getTranslateY(diffTime);
context.translate(translateX, translateY);
context.scale(scale, scale);
context.globalAlpha = getAlpha(diffTime);
// const rotate = getRotate();
// context.rotate(rotate * Math.PI / 180);
context.drawImage(
image,
-image.width / 2,
-image.height / 2,
image.width,
image.height
);
context.restore();
};
}
Изображение, нарисованное здесь, имеет ширину и высоту исходного изображения. Мы установили baseeScale ранее, если изображение больше, мы можем уменьшить масштаб.
const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)];
сканер чертежей в реальном времени
Включите сканер рисования в реальном времени и поместите созданный объект рендеринга в массив renderList.Массив не пуст, что указывает на то, что на холсте все еще есть анимация, поэтому вам нужно продолжать выполнять сканирование до тех пор, пока на холсте не будет анимации. холст.
scan() {
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = "#f4f4f4";
this.context.fillRect(0,0,200,400);
let index = 0;
let length = this.renderList.length;
if (length > 0) {
requestAnimationFrame(this.scan.bind(this));
}
while (index < length) {
const render = this.renderList[index];
if (!render || !render.render || render.render.call(null, (Date.now() - render.timestamp) / render.duration)) {
// 结束了,删除该动画
this.renderList.splice(index, 1);
length--;
} else {
// 当前动画未执行完成,continue
index++;
}
}
}
Вот сравнение времени выполнения, чтобы определить позицию выполнения анимации:
diffTime = (Date.now() - render.timestamp) / render.duration
Если метка времени начала 10000, а текущее время 100100, это означает, что анимация выполнялась в течение 100 миллисекунд.Если анимация должна выполняться в течение 1000 миллисекунд, тогда diffTime = 0,1, что означает, что анимация выполнена на 10%.
Добавить анимацию
Каждый раз, когда вы ставите лайк или получаете от кого-то лайк, метод start вызывается один раз, чтобы сгенерировать экземпляр отрисовки и поместить его в массив экземпляров отрисовки. Если текущий сканер не включен, вам необходимо запустить сканер.Здесь используется переменная сканирования, чтобы предотвратить включение нескольких сканеров.
start() {
const render = this.createRender();
const duration = getRandom(1500, 3000);
this.renderList.push({
render,
duration,
timestamp: Date.now(),
});
if (!this.scanning) {
this.scanning = true;
requestFrame(this.scan.bind(this));
}
return this;
}
не загромождать
При получении большого количества лайков данных, причем лайков много раз подряд (когда зал прямого эфира очень популярен). Тогда рендеринг подобных данных требует особого внимания, иначе страница будет представлять собой кучу подобных анимаций. И соединение не плотное.
thumbsUp(num: number) {
if (num <= this.praiseLast) return;
this.thumbsStart = this.praiseLast;
this.praiseLast = num;
if (this.thumbsStart + 500 < num)
this.thumbsStart = num - 500;
const diff = this.praiseLast - this.thumbsStart;
let time = 100;
let isFirst = true;
if (this.thumbsInter != 0) {
return;
}
this.thumbsInter = setInterval(() => {
if (this.thumbsStart >= this.praiseLast) {
clearInterval(this.thumbsInter);
this.thumbsInter = 0;
return;
}
this.thumbsStart++;
this.thumbsUpAni.start();
if (isFirst) {
isFirst = false;
time = Math.round(5000 / diff);
}
}, time);
},
Запустите таймер здесь и запишите значение thumbsStart, обработанное в таймере.Если есть новые лайки, а таймер все еще работает, непосредственно обновите последнее значение похвалыLast, и таймер обработает все лайки по очереди.
Время задержки таймера определяется тем, сколько похожих анимаций необходимо отрисовать при включении таймера.Например, если нужно отрендерить 100 похожих анимаций, мы распределим 100 похожих анимаций и отрендерим их в течение 5 секунд.
- Для популярных прямых трансляций одновременно будет рендериться множество анимаций, и они не будут отображаться вместе, а анимации могут быть полностью связаны, а анимации будут пузыриться и лайкать анимации постоянно.
- Для непопулярных прямых трансляций, если есть более одного запроса на лайк, мы можем разбить его и отобразить в течение 5 секунд, и он не будет отображаться в кластере.
End
Два способа рендеринга похожей анимации завершены, полный исходный код,Исходный код нажмите здесь.
Исходный код, выполняющий рендеринг:
Здесь вы также можете испытать онлайн-анимацию,кликните сюда
Сравните еще раз
Обе реализации могут соответствовать требованиям, так какая из них лучше?
Давайте посмотрим на сравнение данных двух. Ниже приведено сравнение без аппаратного ускорения и сравнение данных похожей анимации с непрерывным сумасшедшим рендерингом:
В целом отличия следующие:
- Простая реализация CSS3
- Холст более гибкий, а операция более деликатная
- Потребление памяти CSS3 больше, чем у Canvas.Если аппаратное ускорение включено, потребление памяти будет больше.
Добро пожаловать в мой публичный аккаунт WeChat: