Four Ways to Add Watermarks to Web Video, Compared
“Add a watermark to the video” is sentence from product and four completely different engineering routes. Picking wrong means rework: the CSS approach ships, two weeks later you need user IDs — impossible; the FFmpeg burn-in ships, three months later the copy changes — tens of thousands of videos to re-transcode. This article puts all four side by side so you choose right the first time.
Requirements First, Solutions Second
Ask yourself five questions:
- Static or dynamic: a fixed logo, or a moving anti-recording watermark?
- User variables: does the watermark need to show the current viewer’s ID?
- Cost of change: does changing the watermark mean reprocessing video files?
- Clarity: is “good enough” fine, or must it be crisp?
- Budget: ten minutes of frontend, or a backend transcoding pipeline?
Way 1: CSS/DOM Overlay
The intuitive route: an absolutely-positioned div on top of the <video>.
<div class="stage" style="position:relative">
<video src="lesson.mp4" controls></video>
<div class="wm" style="position:absolute;top:8%;right:3%">© MyCourse</div>
</div>
Figure 1: the CSS/DOM overlay — a native video with a positioned div
Pros: ships in ten minutes, pure frontend, instant copy/style changes. Fatal cons: when the player goes fullscreen or picture-in-picture, DOM overlays frequently vanish or misplace; the watermark attaches to the container, not the picture area (it can end up on letterbox bars). Use for: internal tool pages and demos.
Way 2: Canvas Rendering
Draw drawImage(video) frame by frame a Canvas and composite the watermark text — or use Canvas just to generate a watermark sprite. Fully dynamic per frame, at the cost of:
- per-frame CPU/GPU burn — noticeable heat and dropped frames at 4K;
- fullscreen, PiP, and hardware-decode paths each have traps;
- still a “web layer” watermark with CSS-level anti-record value.
Use for: rare effects where the watermark must react to picture content.
Way 3: FFmpeg Burn-In
ffmpeg -i in.mp4 -vf "drawtext=text='© MyCourse':x=10:y=H-th-10" out.mp4
The watermark becomes pixels: every player, every download, every share carries it.
Pros: the “hardest” watermark — even the raw downloaded file has it; zero frontend cost. Cons: one static watermark for every user — no user variables, no tracing; changing it means re-transcoding the entire library, cost growing linearly with catalog size; couples tightly with your HLS/DASH pipeline. Use for: brand-statement watermarks over a stable catalog. Not for traceable paid content.
Way 4: Player-Native Watermarks (ZWPlayer watermarks)
Mature players ship watermarking as a playback capability — ZWPlayer’s watermarks:
new ZWPlayer({
playerElm: 'player',
url: 'lesson.mp4',
watermarks: [
{ type: 'text', behavior: 'static', text: '© MyCourse', dock: 'left', x: '3%', y: '90%' },
{ type: 'text', behavior: 'dynamic', text: '{user_id} · {sys_time}',
movement: { direction: 'random', speed: 3 } },
{ type: 'text', behavior: 'tile', text: 'Internal · {user_id}',
tile: { angle: -30, rowSpacing: 110, colSpacing: 280 } },
],
variables: { user_id: '138****1234' },
});
- Rendered inside the player’s own layer — consistent across fullscreen/PiP/web-fullscreen, tracking the picture area rather than container bars;
- Three behaviors (static/roaming/tile) plus
{user_id}/{sys_time}macros out of the box — the two needs “CSS can’t do, FFmpeg can’t afford” (user variables and dynamic motion) covered at once; - Watermarks live apart from the files — changing them never re-transcodes: edit JSON, site-wide;
- Configure visually in Watermark Studio instead of hand-writing JSON.
The result is the three-layer combo in Figure 4 of Video Anti-Recording: Watermarks, Marquee & Tracing.
Comparison
| CSS overlay | Canvas | FFmpeg burn-in | Player-native | |
|---|---|---|---|---|
| Effort | Minimal | High | Medium (pipeline) | Low (config) |
| Dynamic watermark | Hard | Possible | No | Built-in |
| User variables | Possible (fragile) | Possible | No | Built-in macros |
| Fullscreen/PiP consistency | Often broken | Often broken | Consistent | Consistent |
| Cost to change | Instant | Instant | Full re-transcode | Config edit |
| Watermark survives raw download | No | No | Yes | No |
Conclusions:
- Brand-only mark, stable catalog → FFmpeg burn-in is simplest;
- Internal tool page → CSS overlay suffices;
- Paid content, user tracing, evolving watermark strategy → player-native is the route covering dynamics + variables + low change cost simultaneously;
- Ultimate case (raw downloads must also carry per-user marks) = FFmpeg universal layer + player-native per-user layer, stacked.