目录
一、大文件分片
并行传输:多个数据块可以同时并行传输,利用多线程或多路复用技术提高传输效率;
减少延迟:通过将大文件分割成小块,可以减少单次传输的数据量,从而减少延迟;
以文件上传为例:
根据文件总大小将它分为N组线程,分割后按照下标排序整理,通过Promise.all拿到所有分组数据
//==========================1、index.js=====================
import { cutFile } from "./cutFile.js";
const inpFile = document.querySelector('input[type="file"]');
inpFile.onchange = async (e) => {
const file = e.target.files[0];
const chunks=await cutFile(file);
console.log(chunks,'所有分块的数据');
};
//=======================2、cutFile.js============================
const CHUNK_SIZE = 1024 * 1024 * 5; //5MB
// hardwareConcurrency: 返回当前设备的逻辑处理器数量(即 CPU 核心数)
const THREAD_COUNT = navigator.hardwareConcurrency || 4; //最低4个线程
export function cutFile(file) {
return new Promise((resolve) => {
const chunkCount = Math.ceil(file.size / CHUNK_SIZE); // 分块的数量
const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT); // 总共分为几组线程
const result = new Array(chunkCount); // 初始化结果数组
let finishCount = 0;
for (let i = 0; i < THREAD_COUNT; i++) {
// 创建一个线程,并分配任务
const worker = new Worker("./worker.js", {
type: "module",
});
let end = (i + 1) * threadChunkCount;
const start = i * threadChunkCount;
if (end > chunkCount) {
end = chunkCount;
}
worker.postMessage({
file,
CHUNK_SIZE,
startChunkIndex: start,
endChunkIndex: end,
});
worker.onmessage = (e) => {
// result.push(...e.data) 为啥不直接push ?进程完成时间不确定,会导致顺序错乱
// 将结果放入 result 数组的正确位置:按照下标依次放入
for (let j = start; j < end; j++) {
result[j] = e.data[j - start];
}
worker.terminate();
finishCount++;
if (finishCount === THREAD_COUNT) {
// 所有线程结束后解决 Promise
resolve(result);
}
};
}
});
}
//======================3、worker.js========================
import { creatChunk } from "./creatChunk.js";
onmessage = async (e) => {
const {
file,
CHUNK_SIZE,
startChunkIndex: start,
endChunkIndex: end,
} = e.data;
const proms = [];
for (let i = start; i < end; i++) {
proms.push(creatChunk(file, i, CHUNK_SIZE));
}
const chunks = await Promise.all(proms);
postMessage(chunks); // 发送所有分块的数据
};
//======================4、creatChunk.js========================
//SparkMD5 是一个用于计算 MD5 哈希值的库,它可以处理大文件而不会冻结浏览器界面
// import SparkMD5 from "./sparkmd5.js";
export function creatChunk(file, index, chunkSize) {
return new Promise((resolve) => {
const start = index + chunkSize;
const end = start + chunkSize;//计算当前块的起始和结束索引
// const spark = new SparkMD5.ArrayBuffer();//创建了一个新的 SparkMD5 实例
const fileReader = new FileReader();//FileReader 对象用于读取文件内容
const blob = file.slice(start, end);//从原始文件中切出当前块的 Blob 对象
fileReader.onload = (e) => {
// spark.append(e.target.result);
resolve({
start,
end,
index,
// hash: spark.end(),
blob,
data: e.target.result,
});
};
fileReader.readAsArrayBuffer(blob);//开始读取文件块的内容,并将结果作为 ArrayBuffer
});
}
二、图片添加防篡改水印
2.1、主图+水印图(vue2)
addWatermarkToImage(baseImageUrl, watermarkUrl, callback) {
fetch(baseImageUrl)
.then((response) => response.blob())
.then((blob) => {
const baseImage = new Image()
baseImage.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = baseImage.width
canvas.height = baseImage.height
ctx.drawImage(baseImage, 0, 0, baseImage.width, baseImage.height)
fetch(watermarkUrl)
.then((response) => response.blob())
.then((watermarkBlob) => {
const watermark = new Image()
watermark.onload = () => {
// 计算水印的位置(中间)
const x = (baseImage.width - watermark.width) / 2
const y = (baseImage.height - watermark.height) / 2
// 绘制水印图片
ctx.globalAlpha = 0.2 // 设置水印透明度
ctx.drawImage(
watermark,
x,
y,
watermark.width,
watermark.height
)
canvas.toBlob(callback, 'image/png') // 将带水印的图片转换为 Blob 对象
}
watermark.src = URL.createObjectURL(watermarkBlob)
})
}
baseImage.src = URL.createObjectURL(blob)
})
},
//==========================================添加水印================================
addMark(){
this.addWatermarkToImage(
this.img_url,
this.waterMarkImg,
(watermarkedImageUrl) => {
const url = URL.createObjectURL(watermarkedImageUrl)
console.log(watermarkedImageUrl, '结果',url)
}
)
}
2.2、主图+文字(vue3)
index.vue页面:
<template>
<div class="container">
<waterMark text="版权所有" style="background: #FF9900">
<div class="content"></div>
</waterMark>
<waterMark text="禁止转载" style="background: #28c840">
<div class="content"></div>
</waterMark>
</div>
</template>
<script setup lang="ts">
import waterMark from './watermark.vue'
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
display: flex;
}
</style>
watermark.vue组件:
<template>
<div ref="parentRef" class="watermark-container">
<slot></slot>
</div>
</template>
<script setup>
import { onMounted, ref, defineProps, onUnmounted } from 'vue'
import useWaterMarkBg from './useWaterMarkBg.js'
const parentRef = ref()
const props = defineProps({
text: {
type: String,
require: true,
default: 'watermark',
},
fontSize: {
type: Number,
default: 40,
},
gap: {
type: Number,
default: 20,
},
})
const bg = useWaterMarkBg(props)
let div
// 重置水印
function resetWatermark() {
if (!parentRef.value) {
return
}
if (div) {
div.remove()
}
const { base64, size } = bg.value
console.log('Generated watermark:', base64) // 查看 base64 是否有效
div = document.createElement('div')
div.style.position = 'absolute'
div.style.backgroundImage = `url(${base64})`
div.style.backgroundSize = `${size}px ${size}px`
div.style.backgroundRepeat = 'repeat'
div.style.zIndex = 999
div.style.inset = 0
parentRef.value.appendChild(div) //这样是为了防止从浏览器修改
}
const ob = new MutationObserver((entries) => {
for (const entry of entries) {
// 删除
for (const dom of entry.removedNodes) {
if (dom === div) {
resetWatermark()//如果有人在浏览器删除这个盒子,此时立即重新画一次
return
}
}
// 修改属性
if (entry.target === div) {
resetWatermark()//如果有人在浏览器修改盒子属性,此时立即重新画一次
return
}
}
})
onMounted(() => {
resetWatermark()
ob.observe(parentRef.value, {
childList: true,//观察子节点的变化(添加、删除节点)
subtree: true,//观察整个子树(不仅仅是直接子节点,所有后代节点
attributes: true,//观察属性的变化(例如 div 的属性发生变化时)
})
})
onUnmounted(() => {
ob.disconnect()
})
</script>
<style lang="scss" scoped>
.watermark-container {
width: 400px;
height: 400px;
position: relative;
z-index: 1; /* 确保水印不会覆盖其他内容 */
margin-right: 20px;
}
</style>
其中:
MutationObserver 用于监听 DOM 树的变化
使用 ob.observe() 开始监听父容器(parentRef.value)上的变化
useWaterMarkBg.js代码:
import { computed } from 'vue';
export default function useWaterMarkBg(props) {
return computed(() => {
const canvas = document.createElement('canvas');
const devicePixelRatio = window.devicePixelRatio || 1;
const fontSize = props.fontSize * devicePixelRatio;
const font = `${fontSize}px serif`;
const ctx = canvas.getContext('2d');
ctx.font = font;
const { width } = ctx.measureText(props.text);
const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio;
canvas.width = canvasSize;
canvas.height = canvasSize;
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate((Math.PI / 180) * -45);
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.font = font;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(props.text, 0, 0);
return {
base64: canvas.toDataURL(),
size: canvasSize / devicePixelRatio,
};
});
}