backgroundColor
支持使用
rgb(255,255,255)
,
rgba(255,255,255,1)
,
#fff
等方式设置为纯色,也支持设置为渐变色和纹理填充,具体见
option.color
color
支持的颜色格式:
使用 RGB 表示颜色,比如
'rgb(128, 128, 128)'
,如果想要加上 alpha 通道表示不透明度,可以使用 RGBA,比如
'rgba(128, 128, 128, 0.5)'
,也可以使用十六进制格式,比如
'#ccc'
。
渐变色或者
纹理填充
// 线性渐变,前四个参数分别是 x0, y0, x2, y2, 范围从 0 - 1,相当于在图形包围盒中的百分比,如果 globalCoord 为 `true`,则该四个值是绝对的像素位置
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: 'red' // 0% 处的颜色
offset: 1, color: 'blue' // 100% 处的颜色
global: false // 缺省为 false
// 径向渐变,前三个参数分别是圆心 x, y 和半径,取值同线性渐变
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.5,
colorStops: [{
offset: 0, color: 'red' // 0% 处的颜色
offset: 1, color: 'blue' // 100% 处的颜色
global: false // 缺省为 false
// 纹理填充
image: imageDom, // 支持为 HTMLImageElement, HTMLCanvasElement,不支持路径字符串
repeat: 'repeat' // 是否平铺,可以是 'repeat-x', 'repeat-y', 'no-repeat'
通过一个新的canvas绘制水印,然后在backgroundColor中添加
const waterMarkText = 'YJFicon';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = canvas.height = 100;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.globalAlpha = 0.08;
ctx.font = '20px Microsoft Yahei';
ctx.translate(50, 50);
ctx.rotate(-Math.PI / 4);
ctx.fillText(waterMarkText, 0, 0);
option = {
backgroundColor: {
image: canvas,
repeat: 'repeat'
如果只想在 toolbox.saveAsImage
下载的图片才展示水印,toolbox.feature.saveAsImage
支持配置backgroundColor
,将其设置为水印【纹理】即可
option = {
toolbox: {
show: true,
feature: {
saveAsImage: {
type: 'png',
backgroundColor: {
image: canvas,
repeat: 'repeat'
graphic
除了使用纹理背景,还可以 graphic
添加图形元素,其中包括 text,可用于绘制水印。
option = {
graphic: [
type: 'group',
rotation: Math.PI / 4,
bounding: 'raw',
top: 100,
left: 100,
z: 100,
children: [
type: 'text',
left: 0,
top: 0,
z: 100,
style: {
fill: 'rgba(0,0,0,.2)',
text: 'ECHARTS',
font: 'italic 12px sans-serif'
type: 'text',
left: 40,
top: 40,
z: 100,
style: {
fill: 'rgba(0,0,0,.2)',
text: 'ECHARTS',
font: 'italic 12px sans-serif'
比较繁琐,需要自己设置平铺规律,建议封装一个平铺方法,不能控制区分 saveAsImage。
通过源码可以知道 saveAsImage 的实现也是通过内置 API getConnectedDataURL
获取url(base64格式),然后赋值到 a 标签上(带download属性)实现下载。
class SaveAsImage extends ToolboxFeature<ToolboxSaveAsImageFeatureOption> {
onclick(ecModel: GlobalModel, api: ExtensionAPI) {
const model = this.model;
const title = model.get('name') || ecModel.get('title.0.text') || 'echarts';
const isSvg = api.getZr().painter.getType() === 'svg';
const type = isSvg ? 'svg' : model.get('type', true) || 'png';
const url = api.getConnectedDataURL({
type: type,
backgroundColor: model.get('backgroundColor', true)
|| ecModel.get('backgroundColor') || '#fff',
connectedBackgroundColor: model.get('connectedBackgroundColor'),
excludeComponents: model.get('excludeComponents'),
pixelRatio: model.get('pixelRatio')
const browser = env.browser;
if (isFunction(MouseEvent) && (browser.newEdge || (!browser.ie && !browser.edge))) {
const $a = document.createElement('a');
$a.download = title + '.' + type;
$a.target = '_blank';
$a.href = url;
const evt = new MouseEvent('click', {
view: document.defaultView,
bubbles: true,
cancelable: false
$a.dispatchEvent(evt);
else {
可以通过扩展此方法,先获取原始的图片url,基于这个图片重新绘制一个canvas,然后在这个基础上覆盖水印,最后将canvas再次转成url返回。
echartsInstance._api.getConnectedDataURL
echartsInstance.__proto__.getConnectedDataURL
含有两处实例方法需要处理。
✅可以拦截默认行为获取到添加水印后的图片url
❌通过原api方法获取到url后绘制到新的canvas,涉及到异步处理(img标签需要等待load),由于 saveAsImage 调用 getConnectedDataURL
获取url是同步过程,因此无法正确读取到异步处理完的最终url,导致下载失败;不过,手动调用实例getConnectedDataURL
可以使用,需要配置Promise语法使用。
const originGetConnectedDataURL = echartsInstance._api.getConnectedDataURL
echartsInstance._api.getConnectedDataURL = async function () {
const origin = originGetConnectedDataURL.call(echartsInstance, ...arguments)
const result = await toWaterUrl(origin)
return result
function toWaterUrl (url) {
return new Promise(resolve => {
const img = new Image()
img.src = url + '?v=' + Math.random();
img.setAttribute('crossOrigin', 'Anonymous');
img.onload = function() {
resolve(afterWater(drawCanvas(img))
附带drawCanvas/afterWater
function drawCanvas(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
return canvas;
function afterWater(canvas, { text = 'WangSu', font = 'italic 10px', fillStyle = 'red', rotate = -30 }, type = 'png') {
return new Promise((resolve, reject) => {
let context = canvas.getContext('2d');
context.font = font;
context.fillStyle = fillStyle;
context.rotate(rotate * Math.PI / 180);
context.textAlign = 'center';
context.textBaseline = 'Middle';
const textWidth = context.measureText(text).width;
for (let i = (canvas.height * 0.5) * -1; i < 800; i += (textWidth + (textWidth / 5))) {
for (let j = 0; j < canvas.height * 1.5; j += 128) {
context.fillText(text, i, j);
resolve(canvas.toDataURL('image/' + type))
wangsd
粉丝