Canvas Watermark — Add Text Watermark to Images with JavaScript
Add a repeating diagonal watermark to any image. Type custom text, adjust opacity and font size, then export as PNG.
Instructions: Edit the text field, adjust sliders, click Export to download.
- Vanilla
- React
- Vue
import Konva from 'konva'; var width = window.innerWidth; var height = window.innerHeight; var stage = new Konva.Stage({ container: 'container', width: width, height: height, }); var layer = new Konva.Layer(); stage.add(layer); var container = document.getElementById('container'); var controls = document.createElement('div'); controls.style.cssText = 'display:flex;gap:8px;align-items:center;margin-bottom:6px;flex-wrap:wrap;font:12px Arial,sans-serif;'; controls.appendChild(document.createTextNode('Text:')); var textInput = document.createElement('input'); textInput.type = 'text'; textInput.value = 'SAMPLE'; textInput.style.cssText = 'padding:3px 5px;width:90px;font-size:12px;'; controls.appendChild(textInput); var opacityLabel = document.createElement('label'); opacityLabel.style.cssText = 'display:flex;align-items:center;gap:4px;'; opacityLabel.appendChild(document.createTextNode('Opacity:')); var opacitySlider = document.createElement('input'); opacitySlider.type = 'range'; opacitySlider.min = '0.05'; opacitySlider.max = '1'; opacitySlider.step = '0.05'; opacitySlider.value = '0.3'; opacitySlider.style.width = '70px'; opacityLabel.appendChild(opacitySlider); var opacityVal = document.createElement('span'); opacityVal.textContent = '0.3'; opacityVal.style.minWidth = '24px'; opacityLabel.appendChild(opacityVal); controls.appendChild(opacityLabel); var sizeLabel = document.createElement('label'); sizeLabel.style.cssText = 'display:flex;align-items:center;gap:4px;'; sizeLabel.appendChild(document.createTextNode('Size:')); var sizeSlider = document.createElement('input'); sizeSlider.type = 'range'; sizeSlider.min = '14'; sizeSlider.max = '60'; sizeSlider.step = '2'; sizeSlider.value = '28'; sizeSlider.style.width = '70px'; sizeLabel.appendChild(sizeSlider); var sizeVal = document.createElement('span'); sizeVal.textContent = '28'; sizeVal.style.minWidth = '20px'; sizeLabel.appendChild(sizeVal); controls.appendChild(sizeLabel); var exportBtn = document.createElement('button'); exportBtn.textContent = 'Export PNG'; exportBtn.style.cssText = 'padding:4px 10px;cursor:pointer;font-size:12px;background:#333;color:white;border:none;border-radius:3px;'; controls.appendChild(exportBtn); container.parentNode.insertBefore(controls, container); var watermarkGroup = new Konva.Group(); var imageObj = new Image(); imageObj.onload = function() { var bgImage = new Konva.Image({ image: imageObj, width: width, height: height, }); layer.add(bgImage); layer.add(watermarkGroup); drawWatermarks(); }; imageObj.src = 'https://konvajs.org/assets/landscape.jpg'; function drawWatermarks() { watermarkGroup.destroyChildren(); var text = textInput.value || 'SAMPLE'; var opacity = parseFloat(opacitySlider.value); var size = parseInt(sizeSlider.value); for (var x = -width; x < width * 2; x += 180) { for (var y = -height; y < height * 2; y += 120) { watermarkGroup.add(new Konva.Text({ x: x, y: y, text: text, fontSize: size, fontFamily: 'Arial', fill: 'white', opacity: opacity, rotation: -30, })); } } layer.draw(); } textInput.addEventListener('input', drawWatermarks); opacitySlider.addEventListener('input', function() { opacityVal.textContent = parseFloat(this.value).toFixed(2); drawWatermarks(); }); sizeSlider.addEventListener('input', function() { sizeVal.textContent = this.value; drawWatermarks(); }); exportBtn.addEventListener('click', function() { var link = document.createElement('a'); link.href = stage.toDataURL({ pixelRatio: 2 }); link.download = 'watermarked.png'; link.click(); });
import { Stage, Layer, Image, Text, Group } from 'react-konva'; import { useState, useRef, useEffect } from 'react'; var App = function() { var stageRef = useRef(null); var [text, setText] = useState('SAMPLE'); var [opacity, setOpacity] = useState(0.3); var [fontSize, setFontSize] = useState(28); var [image, setImage] = useState(null); var width = window.innerWidth; var height = window.innerHeight; useEffect(function() { var img = new window.Image(); img.onload = function() { setImage(img); }; img.src = 'https://konvajs.org/assets/landscape.jpg'; }, []); var positions = []; for (var x = -width; x < width * 2; x += 180) { for (var y = -height; y < height * 2; y += 120) { positions.push({ x: x, y: y }); } } var handleExport = function() { var link = document.createElement('a'); link.href = stageRef.current.toDataURL({ pixelRatio: 2 }); link.download = 'watermarked.png'; link.click(); }; return ( <div> <div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '6px', flexWrap: 'wrap', font: '12px Arial,sans-serif' }}> <span>Text:</span> <input type="text" value={text} onChange={function(e) { setText(e.target.value); }} style={{ padding: '3px 5px', width: '90px', fontSize: '12px' }} /> <label style={{ display: 'flex', alignItems: 'center', gap: '4px' }}> Opacity: <input type="range" min="0.05" max="1" step="0.05" value={opacity} onChange={function(e) { setOpacity(parseFloat(e.target.value)); }} style={{ width: '70px' }} /> <span style={{ minWidth: '24px' }}>{opacity.toFixed(2)}</span> </label> <label style={{ display: 'flex', alignItems: 'center', gap: '4px' }}> Size: <input type="range" min="14" max="60" step="2" value={fontSize} onChange={function(e) { setFontSize(parseInt(e.target.value)); }} style={{ width: '70px' }} /> <span style={{ minWidth: '20px' }}>{fontSize}</span> </label> <button onClick={handleExport} style={{ padding: '4px 10px', cursor: 'pointer', fontSize: '12px', background: '#333', color: 'white', border: 'none', borderRadius: '3px' }}>Export PNG</button> </div> <Stage ref={stageRef} width={width} height={height}> <Layer> {image && <Image image={image} width={width} height={height} />} <Group> {positions.map(function(pos, i) { return <Text key={i} x={pos.x} y={pos.y} text={text} fontSize={fontSize} fontFamily="Arial" fill="white" opacity={opacity} rotation={-30} />; })} </Group> </Layer> </Stage> </div> ); }; export default App;
<template> <div> <div style="display:flex;gap:8px;align-items:center;margin-bottom:6px;flex-wrap:wrap;font:12px Arial,sans-serif;"> <span>Text:</span> <input v-model="text" type="text" style="padding:3px 5px;width:90px;font-size:12px;" /> <label style="display:flex;align-items:center;gap:4px;"> Opacity: <input v-model.number="opacity" type="range" min="0.05" max="1" step="0.05" style="width:70px;" /> <span style="min-width:24px;">{{ opacity.toFixed(2) }}</span> </label> <label style="display:flex;align-items:center;gap:4px;"> Size: <input v-model.number="fontSize" type="range" min="14" max="60" step="2" style="width:70px;" /> <span style="min-width:20px;">{{ fontSize }}</span> </label> <button @click="handleExport" style="padding:4px 10px;cursor:pointer;font-size:12px;background:#333;color:white;border:none;border-radius:3px;">Export PNG</button> </div> <v-stage ref="stageRef" :config="stageConfig"> <v-layer> <v-image v-if="image" :config="imageConfig" /> <v-group> <v-text v-for="(pos, i) in positions" :key="i" :config="{ x: pos.x, y: pos.y, text: text, fontSize: fontSize, fontFamily: 'Arial', fill: 'white', opacity: opacity, rotation: -30 }" /> </v-group> </v-layer> </v-stage> </div> </template> <script setup> import { ref, computed, onMounted } from 'vue'; var text = ref('SAMPLE'); var opacity = ref(0.3); var fontSize = ref(28); var image = ref(null); var stageRef = ref(null); var stageConfig = computed(function() { return { width: window.innerWidth, height: window.innerHeight }; }); var imageConfig = computed(function() { return { image: image.value, width: window.innerWidth, height: window.innerHeight }; }); var positions = computed(function() { var width = window.innerWidth; var height = window.innerHeight; var arr = []; for (var x = -width; x < width * 2; x += 180) { for (var y = -height; y < height * 2; y += 120) { arr.push({ x: x, y: y }); } } return arr; }); onMounted(function() { var img = new window.Image(); img.onload = function() { image.value = img; }; img.src = 'https://konvajs.org/assets/landscape.jpg'; }); function handleExport() { if (!stageRef.value) return; var stage = stageRef.value.getNode(); var link = document.createElement('a'); link.href = stage.toDataURL({ pixelRatio: 2 }); link.download = 'watermarked.png'; link.click(); } </script>