Watermark
Overview
SWatermark overlays a repeating text or image pattern on top of page content to indicate ownership, confidentiality, or branding. It generates a tiled canvas data URL as the overlay’s background-image, supports text and image watermarks with rotation, gap, offset, and cross-pattern configuration, and includes an optional anti-tamper defense mechanism that automatically restores the overlay if it is removed or modified via DevTools.
Note: In addition to
SWatermark, the Aria layer also exportsWatermarkCompactfor the default root/overlay structure, plusWatermarkRoot,WatermarkOverlay, andprovideWatermarkUifor fully custom composition and style injection.
Usage
Features
- 📝 Text watermarks — Render repeating text with configurable font size, family, color, and weight.
- 🖼️ Image watermarks — Pass an
imageURL to tile an image pattern; supportscrossOrigin: 'anonymous'for CORS-enabled sources. - 🔄 Rotation — Rotate watermarks by any angle (default
-22°). - 📐 Gap and offset — Control tile spacing via
gap: [x, y]and positioning viaoffset: [x, y]. - ✖️ Cross pattern — Set
cross: trueto render two overlapping watermarks along both diagonals. - 🛡️ Anti-tamper defense — When
defense: true,MutationObserverdetects overlay removal or attribute tampering and automatically repairs the overlay by re-rendering it. - 🖥️ Fullscreen mode — Set
fullscreen: trueto fix the overlay to the viewport instead of the parent container. - 🔧 Aria composition — Export
WatermarkRoot+WatermarkOverlay+WatermarkCompactfor custom layouts; inject styles viaprovideWatermarkUi. - 🌐 SSR-safe — Canvas generation guarded by
typeof window === 'undefined'; overlay not rendered on server.
Demos
Basic
This content is protected by a repeating text watermark.
Cross
Cross pattern creates a denser watermark with two overlapping diagonal layers.
Fullscreen
Fullscreen mode positions the watermark overlay fixed to the viewport, so it remains visible even when content scrolls.
API
Aria Layer
4Watermark
Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | ClassValue | — | The class attribute for the root element. |
ui | Partial<WatermarkUi> | — | UI class overrides for each slot. |
fullscreen | boolean | false | When true, the watermark overlay is fixed to the viewport instead of the parent container. |
overlayProps | WatermarkOverlayProps | — | Properties forwarded to the overlay element. |
content | string | — | The text content of the watermark. |
image | string | — | The image URL of the watermark.
When both content and image are provided, image takes priority. |
fontSize | number | 16 | The font size of the watermark text in pixels. |
fontFamily | string | 'sans-serif' | The font family of the watermark text. |
fontColor | string | 'rgba(0, 0, 0, 0.15)' | The font color of the watermark text. |
fontWeight | string | number | 'normal' | The font weight of the watermark text. |
rotate | number | -22 | The rotation angle of the watermark in degrees. |
gap | [number, number] | [100, 100] | The gap between watermark tiles in pixels, as [x, y]. |
offset | [number, number] | [0, 0] | The offset of the watermark within each tile in pixels, as [x, y]. |
width | number | — | The width of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
height | number | — | The height of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
cross | boolean | false | When true, renders a diagonal cross pattern with two overlapping watermarks. |
defense | boolean | false | When true, observes and restores the watermark overlay if it is tampered with or removed. |
WatermarkCompact
Props
| Prop | Type | Default | Description |
|---|---|---|---|
overlayProps | WatermarkOverlayProps | — | Properties forwarded to the overlay element. |
content | string | — | The text content of the watermark. |
image | string | — | The image URL of the watermark.
When both content and image are provided, image takes priority. |
fontSize | number | 16 | The font size of the watermark text in pixels. |
fontFamily | string | 'sans-serif' | The font family of the watermark text. |
fontColor | string | 'rgba(0, 0, 0, 0.15)' | The font color of the watermark text. |
fontWeight | string | number | 'normal' | The font weight of the watermark text. |
rotate | number | -22 | The rotation angle of the watermark in degrees. |
gap | [number, number] | [100, 100] | The gap between watermark tiles in pixels, as [x, y]. |
offset | [number, number] | [0, 0] | The offset of the watermark within each tile in pixels, as [x, y]. |
width | number | — | The width of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
height | number | — | The height of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
cross | boolean | false | When true, renders a diagonal cross pattern with two overlapping watermarks. |
defense | boolean | false | When true, observes and restores the watermark overlay if it is tampered with or removed. |
WatermarkOverlay
WatermarkRoot
Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | — | The text content of the watermark. |
image | string | — | The image URL of the watermark.
When both content and image are provided, image takes priority. |
fontSize | number | 16 | The font size of the watermark text in pixels. |
fontFamily | string | 'sans-serif' | The font family of the watermark text. |
fontColor | string | 'rgba(0, 0, 0, 0.15)' | The font color of the watermark text. |
fontWeight | string | number | 'normal' | The font weight of the watermark text. |
rotate | number | -22 | The rotation angle of the watermark in degrees. |
gap | [number, number] | [100, 100] | The gap between watermark tiles in pixels, as [x, y]. |
offset | [number, number] | [0, 0] | The offset of the watermark within each tile in pixels, as [x, y]. |
width | number | — | The width of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
height | number | — | The height of a single watermark tile in pixels.
When not provided, it is calculated automatically based on text dimensions and gap. |
cross | boolean | false | When true, renders a diagonal cross pattern with two overlapping watermarks. |
defense | boolean | false | When true, observes and restores the watermark overlay if it is tampered with or removed. |
Notes
Architecture and benchmark differences
SWatermark splits into an Aria layer that owns canvas generation, overlay state, and defense logic, and a styled layer that owns the watermarkVariants scv() recipe (root: relative, overlay: absolute inset-0 pointer-events-none bg-repeat). The Aria WatermarkCompact composes WatermarkRoot + WatermarkOverlay and provides the repairOverlay function for defense.
| Aspect | VeanUI | Ant Design Watermark | Element Plus Watermark | MUI Watermark |
|---|---|---|---|---|
| Architecture | Aria + styled split | styled only | styled only | styled only |
| Text watermark | ✅ | ✅ | ✅ | ✅ |
| Image watermark | ✅ crossOrigin: anonymous | ✅ | ✅ | — |
| Cross pattern | ✅ cross prop | — | — | — |
| Anti-tamper defense | ✅ MutationObserver (removal + attribute) | ✅ MutationObserver | ✅ MutationObserver | — |
| Fullscreen mode | ✅ fullscreen prop | ✅ inherit | ✅ content slot | — |
| Aria export | ✅ Root + Overlay + Compact | — | — | — |
| Gap/offset control | ✅ gap + offset | ✅ gap + offset | ✅ gap + offset | ✅ gap |
Runtime cautions
- Canvas requirement: Watermark generation uses
<canvas>andcanvas.toDataURL(). In environments without canvas support (e.g., some SSR setups), the overlay will not render —generateWatermarkDataUrlreturnsundefined. - CORS for images: Image watermarks use
crossOrigin = 'anonymous'. The image server must send appropriate CORS headers (Access-Control-Allow-Origin), otherwise the canvas becomes tainted andtoDataURL()throws. - Defense is opt-in: The
defenseprop defaults tofalse. Enable it withdefense: trueto activateMutationObserver-based tamper detection. Defense has a small performance cost due to observer callbacks. - Defense scope: The defense mechanism detects overlay removal (via root’s
childListobserver) and attribute tampering (via overlay’sattributesobserver). It checksaria-hidden,class,style,hidden, anddata-vean-watermark-overlayattribute. If any is modified, the overlay is re-rendered via a:keyincrement. fullscreenpositioning: Whenfullscreen: true, the overlay switches fromabsolute inset-0(parent-relative) tofixed inset-0 z-9999(viewport-fixed), covering the entire screen.
Aria Composition
When the default root/overlay structure is enough, import WatermarkCompact from @vean/aria/watermark. If you need separate control over the root and overlay elements, compose the Aria primitives directly:
<script setup lang="ts">
import { computed } from 'vue';
import { WatermarkOverlay, WatermarkRoot, provideWatermarkUi } from '@vean/aria';
const ui = computed(() => ({
root: 'relative',
overlay: 'absolute inset-0 pointer-events-none bg-repeat'
}));
provideWatermarkUi(ui);
</script>
<template>
<WatermarkRoot content="CONFIDENTIAL" :rotate="-22">
<slot />
<WatermarkOverlay />
</WatermarkRoot>
</template>FAQ
How do I add a watermark to the entire page? Set fullscreen: true: <SWatermark content="CONFIDENTIAL" fullscreen />. The overlay switches to fixed inset-0 z-9999, covering the viewport.
How do I prevent users from removing the watermark via DevTools? Enable defense: <SWatermark content="CONFIDENTIAL" defense />. The component uses MutationObserver to detect overlay removal or attribute tampering and automatically re-renders the overlay. Note that this is not foolproof — determined users can still disable JavaScript or block observers.
Can I use an image as the watermark pattern? Yes. Pass the image prop: <SWatermark image="/logo.png" />. The image is loaded with crossOrigin = 'anonymous', so the server must send CORS headers. When both content and image are provided, image takes priority.
What is the cross pattern? Set cross: true to render two overlapping watermarks — one along the main diagonal and one along the anti-diagonal with opposite rotation. This creates a denser, cross-shaped pattern.
How do I control the tile spacing? Use the gap prop: <SWatermark content="DRAFT" :gap="[200, 150]" />. The first value is horizontal spacing, the second is vertical. Use offset to shift the watermark within each tile.