canvas自適應文字長度,旋轉角度生成水印背景圖
- 設定canvas字型大小後,透過
ctx.measureText(text).width
獲取兩行文字的寬度text1,text2,取最大寬度為文字框寬度textWidth - 設定兩行文字間距,可得文字框高度:
textHeight=2*fontsize+ space_line
- 計算最小一個能夠完全包裹旋轉後文字的盒子寬高
已知旋轉角度為rotate=>得到弧度rad = (rotate*Math.pi) /180
單個水印圖平鋪成為蒙層的背景圖,space_x,space_y用於調整水印之間的間距
function drawWatermark(el, config = {}) {
if (!el) return;
// 預設配置
let {
text1 = '今天也要保持愉悅鴨~', //文字1
text2 = '2022-12-07', // 文字2
space_x = 0, // x軸間距
space_y = 0, // y軸間距
space_line = 20, //兩列文字的間距
font = 'Microsoft JhengHei bold',
fontSize = 40, // 字型
color = 'rgba(22,22,22,1)', // 字色
rotate = 30 // 傾斜度
} = config;
const canvas = document.createElement('canvas');
el.appendChild(canvas);
const ctx = canvas .getContext('2d');
ctx.font = fontSize + 'px ' + font; //設定好fontsize才能正確計算出文字寬度
let tw1= ctx.measureText(text1).width;
let tw2= ctx.measureText(text2).width;
let textWidth = Math.max(tw1, tw2); //文字最長寬度為文字框寬度
let textHeight = fontSize * 2 + space_line; //文字框高度為兩個文字+行間距
let rad = (rotate * Math.PI) / 180; //角度轉弧度
let sin = Math.sin(rad );
let cos = Math.cos(rad );
let width = textWidth * cos + textHeight * sin + space_x ; //為包裹住文字框的最小盒子寬度
let height = textWidth * sin + textHeight * cos + space_y; //為包裹住文字框的最小盒子高度
canvas.width = width;
canvas.height = height;
canvas.style.cssText = `width:${width}px;height:${height}px;display:none;`;
ctx.translate(space_x , textWidth * sin + space_y ); // 移動旋轉中心
ctx.rotate((-1 * (rotate * Math.PI)) / 180); //旋轉文字框
ctx.fillStyle = color;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.font = fontSize + 'px ' + font;
ctx.fillText(text1, (textWidth - tw1) / 2, 0.5 * fontSize); //文字在文字框中居中顯示
ctx.fillText(text2, (textWidth - tw2) / 2, 1.5 * fontSize + space_line); //文字在文字框中居中顯示
return canvas.toDataURL('image/png');
},
生成蒙層
在目標元素下新增一個相對定位的子元素,將水印圖片平鋪作為背景圖。
禁止蒙層的刪除和修改
- 刪除或移動element
- 修改style
transform: translate(100%,100%);
display: none;
visibility: hidden;
取消背景圖
function createMask(el) {
//建立蒙層
let $mask = document.createElement('div');
//判斷蒙層父元素是否有定位
let position = window.getComputedStyle(el, null).position;
if (position === 'static') {
el.style.position = 'relative';
}
//設定蒙層樣式
let style = `visibility: visible !important;
transform: translate(0,0) !important;
display: block !important;
visibility: visible !important;
width: 100% !important;
height: 100% !important;
pointer-events: none !important;
background-color: rgba(0, 0, 0, 0)!important;
background-repeat: repeat !important;
position: absolute !important;
top: 0px !important;
left: 0px !important;
z-index: 999 !important;
background-image: url(${drawWatermark(el)}) !important`;
$mask.setAttribute('style', style);
//新增蒙層
el.append($mask);
// 建立MutationObserver
el.observer = new MutationObserver((mutationRecord) => {
//處理DOM
mutationRecord.forEach((mutation) => {
// 蒙層刪除或者被移動到別處
if (mutation.target === el && mutation.removedNodes[0] == $mask) {
el.append($mask);
} else if (mutation.target == $mask && mutation.attributeName === 'style') {
// 蒙層被更改樣式 在監聽到蒙層樣式更改後,賦值的新的樣式會導致再次觸發監聽回撥,所以需要在監聽事件中判斷何時需要賦值
const changestyle = $mask?.getAttribute('style');
if (changestyle !== style) {
$mask.setAttribute('style', style);
}
}
});
});
// 啟動監控
el.observer.observe(el, {
childList: true,
attributes: true,
subtree: true
});
return $mask;
},
行內樣式加important是為了防止透過新增class或其他css覆蓋樣式(暫時沒有找到怎麼如何透過修改css的方式更改樣式的監聽方式)
踩得一個坑
設定元素的行內樣式有很多種
let style = 'width:100%;height:100%' 適用單個樣式更改
element.style.width =100% ;element.style['height'] =100%
element.style.cssText = style
element.setAttribute('style',style )
方式二設定樣式後,行內樣式格式和賦值時的style的格式不一樣,獲取到行內style後直接進行===判斷,回造成死迴圈
解決方法:
- 第一次監聽到蒙層更改時,立刻移除蒙層,重新生成新蒙層
- 寫個函式判斷不同格式的兩個樣式屬性上是否相等