background
Before I wrote a simple single bullet to achieve the article, for bilibili such a video site bullet, how to achieve it? How to push an input bullet screen into the current screen, select the appropriate position to push, ensure that the current bullet screen is not repeated with the bullet screen?
Problems that need to be solved
- Multi-track bullet screen
- Distribute the existing bullet lists evenly on multiple tracks
- The bullet screens on each track remain non-overlapping as they move along the track
- Some other functions, such as control of the style of the barrage, the speed of the barrage, the motion state of the barrage (whether to stop)
implementation
For the bullet screen component, it can be made into two forms: controlled component and uncontrolled component
- Controlled components Components expose hooks that can change the internal state and behavior of control components
- After initial properties are passed in from the outside of an uncontrolled component, the behavior and internal state of the component are managed entirely by itself
Depending on your business needs, you can choose how this component is implemented, and in this article, this component is implemented as an uncontrolled component
Component Property Definition
export interface IBarrageProps {
// The default height of the bullet screen is 50 px
trackHeight: number
// The default number of bullet tracks is 4
trackLines: number
// Start to play the bullet callbackonStart? :() = > void
// Finish playing the bullet screen callbackonEnd? :() = > void
// Single bullet display time of each track. If it is an array, it means that each track defines its own bullet display time s
duration: number | number[]
// For the collection of bullet screens added to the bullet screen track
barrageList: IBarrageItem[]
TrackLineIndex: track number 1, 2, 3, 4renderItem? :(item: IBarrageItem, trackLineIndex) = >ReactNode className? :string
// Control whether the barrage stopsautoScroll? :boolean
}
export interface IBarrageItem {
text: stringavatar? :string
}
Copy the code
TrackHeight * trackLines is the height of the barrage container
Bullet screen movement mode
For a single bullet screen, there are two modes of motion after being pushed into a track
-
Fixed speed of the barrage
With a preset speed of uniform motion, so that the speed of each track is the same, to be pushed into the bullet screen as long as the end of the last bullet screen in the orbit has been fully into the orbit after the push, can ensure that in the process of motion will not overlap with the last bullet screen;
-
Fixed time of the barrage
The time between the beginning of the barrage entering the orbit and the end completely leaving the orbit is fixed for each barrage
duration
;
The first mode is relatively simple to implement, so we will focus on the second mode of motion
Fixed time of the barrage
After the content of each bullet screen on each track is determined, some properties of the bullet screen itself have been determined: container width + bullet screen width = distance that the bullet screen needs to move; bullet screen speed = distance that the bullet screen needs to move; bullet screen movement time: duration
The time when each bullet screen starts to be pushed is denoted as startTime
After each frame of the bullet screen where the position is determined
dt = Date.now() - startTime
The distance of the barrage movement until the current frame
distance = speed * dt
export interface IBarrageItemProps {
// The unique id of the barrage
key: string
// Screen element width px
width: number
// The height of the barrage element is px
height: number
// The current offset is px
distance: number
// The display duration of the barrage element is s
duration: number
// This parameter is set after the width of the bullet screen is set
speed: number
// The timing of the barrage element relative to the start time ms
startTime: number
// Barrage list information
item: IBarrageItem
}
Copy the code
So the problem becomes two
- How do you know the width of a barrage before it is pushed in
- Whether the bullet screen can be pushed into the current orbit and ensure that the bullet screen to be pushed and the existing bullet screen on the orbit do not overlap
Train of thought
How do you know the width of a barrage before it is pushed in
To know to push into the barrage of width, of course, to be rendered in the browser to get the position of the barrage size data, for the incoming barrage of tabular data, all render barrage if once, you can get all the style of the barrage data, but can bring a lot of rendering overhead, this is not acceptable, in order to solve this problem, can provide a temporary container, It is used to render the current bullet screen to be pushed, and obtain the size and position of the bullet screen. During each frame, the bullet screen to be pushed is placed in a temporary container to determine whether it can be pushed into a certain track. If so, the next frame continues the judgment of the next bullet screen; If not, the next frame continues to judge the barrage; const { width, height } = tempBarrageElementRef.current.getBoundingClientRect()
Whether the bullet screen can be pushed into the current orbit and ensure that the bullet screen to be pushed and the existing bullet screen on the orbit do not overlap
To judge whether the bullet screen can be pushed into the current orbit and ensure that the bullet screen to be pushed into the orbit and the existence of the bullet screen does not overlap, only need to judge the current bullet screen to be pushed into the current orbit and the latest bullet screen pushed into the current orbit comparison, if not overlapping judgment, then you can be pushed into the current orbit;
Nonoverlapping judgment
In fact, is the junior high school chase and problem
const checkBarrage = useCallback((trackLineIndex: number.waitPushBarrage: IBarrageItemProps): boolean= > {
// Determine whether the bullet screen can be placed on the currently selected orbit
// containerPos Barrage container property existBarrageList pushes the barrage data
const currentBarrageTrack = existBarrageList[trackLineIndex]
const lastBullet = currentBarrageTrack[currentBarrageTrack.length - 1]
if (lastBullet && containerPos) {
const { startTime, speed, width } = lastBullet
const now = Date.now()
const distance = (now - startTime) / 1000 * speed
const rightPos = containerPos.right + width - distance
const leftPos = containerPos.right - distance
// The last element in the orbit is required to be in the display area with a free distance of 15px
if (rightPos > containerPos.right - 10) {
return false
}
// Basic formula: s = v * t
const lastS = leftPos - containerPos.left + width
const lastT = lastS / speed
const newS = containerPos.width - 10 // Leave a space of 10px
const newT = newS / waitPushBarrage.speed
// Trace the problem
if (speed < waitPushBarrage.speed && lastT > newT) {
return false}}return true
}, [containerPos, existBarrageList])
Copy the code
// Container properties
export interface ContainerPops {
width: number
height: number
top: number
left: number
right: number
bottom: number
}
Copy the code
The pushed barrage moves out of the container and should be removed
newExistBarrageList = newPositionExistBarrageList.map(trackBarrageList => trackBarrageList.filter(item => item.distance <= item.width + containerpos.width + 100)
animation
As far as animating each frame is concerned, it must be implemented with requestAnimationFrame