1、canvas如何压缩图片?
Canvas通过将图片绘制到画布并调整尺寸或质量参数来压缩图片。具体实现步骤如下:
核心压缩原理
- 尺寸调整:将图片绘制到指定宽高的canvas画布上,通过等比缩放减少像素量"1""2"。例如设置最大宽度800px,超出时按比例缩小高度。
- 质量控制:使用
canvas.toDataURL()或toBlob()方法时,通过第二个参数(0-1范围)降低图片质量。例如JPEG格式设置0.7可显著减小体积"3""4"。
实现步骤
-
加载图片
- 通过
FileReader读取文件或new Image()加载图片路径"1""5"。
- 通过
-
绘制到Canvas
- 创建离屏canvas元素,设置目标宽高。
- 使用
ctx.drawImage(img, 0, 0, targetWidth, targetHeight)重绘图片"3""6"。
-
导出压缩结果
toDataURL('image/jpeg', 0.8):输出Base64编码字符串(适合小图)。toBlob(callback, 'image/jpeg', 0.7):生成Blob对象(适合上传)
2、canvas如何把png图的透明部分变成渐变色
要在Canvas中将PNG图的透明部分变成渐变色,可以通过以下步骤实现:
- 加载PNG图像。
- 使用Canvas获取图像的像素数据。
- 修改透明区域的像素颜色为所需的渐变色。
- 将修改后的图像重新绘制到Canvas上。
3、promise和async awaite区别,错误捕获如何做?
区别总结
-
语法:
Promise: 使用.then()和.catch()来处理结果和错误。async/await: 使用async和await关键字,看起来更像是同步代码。
-
可读性:
Promise: 可能会导致“回调地狱”,尤其是在多个异步操作嵌套的情况下。async/await: 提高了代码的可读性和可维护性,特别是在需要处理多个异步操作时。
-
错误处理:
Promise: 使用.catch()来捕获错误。async/await: 使用try/catch块来捕获错误。
4、promise的catch捕捉的错误是最后一条还是整条链路的错误
Promise 的 .catch() 捕获的是整条链路中所有未被处理的错误,而非仅仅最后一条。
具体来说,当 Promise 链式调用(.then().then().catch())中任何一个环节抛出错误(或状态变为 rejected),且该错误没有被前面的 .then() 第二个参数(错误回调)处理时,会一直向后传递,最终被链路末端的 .catch() 捕获。
示例说明
javascript
运行
Promise.resolve()
.then(() => {
console.log("第一步");
throw new Error("第一步出错"); // 抛出错误
})
.then(() => {
console.log("第二步"); // 不会执行(因为第一步出错)
})
.catch((err) => {
console.error("捕获到错误:", err.message); // 输出:"捕获到错误: 第一步出错"
});
在这个例子中:
- 第一步抛出的错误没有被当前
.then()的第二个参数处理,所以会向后传递。 - 第二步的
.then()不会执行,错误直接传递到最后的.catch()并被捕获。
关键特性
- 错误冒泡:链路中任何位置的错误都会像 “冒泡” 一样向后传递,直到被某个
.catch()或.then()的错误回调捕获。 - 就近捕获:如果链路中间有
.catch(),则错误会被最近的.catch()捕获,后续链路可继续执行(类似同步代码中的try/catch后继续运行)。
javascript
运行
Promise.resolve()
.then(() => {
throw new Error("错误A");
})
.catch((err) => {
console.error("捕获A:", err.message); // 捕获错误A,阻止其继续传递
return "恢复正常"; // catch 中返回正常值会让 Promise 状态变为 resolved
})
.then((res) => {
console.log("后续步骤:", res); // 执行:"后续步骤: 恢复正常"
throw new Error("错误B");
})
.catch((err) => {
console.error("捕获B:", err.message); // 捕获错误B
});
结论
.catch() 捕获的是从当前位置向前追溯,整条链路中所有未被处理的错误,并非仅最后一个错误。这使得 .catch() 可以作为整条链路的 “统一错误处理中心”,简化错误管理。
5、大文件上传如何做断点续传
文件上传的断点续传核心是将文件分片传输,并通过 “记录已上传分片” 和 “校验分片完整性” 实现中断后继续上传,避免重复传输整个文件。以下是具体实现方案和关键步骤:
一、核心原理
- 文件分片:将大文件(如 1GB)按固定大小(如 5MB)分割成多个小分片(Blob/File 对象),降低单次传输压力。
- 唯一标识:为每个文件生成唯一 ID(如通过 MD5 哈希文件内容、结合文件名 + 大小 + 修改时间),确保服务器能识别同一文件的分片。
- 状态记录:上传前向服务器查询 “该文件已上传的分片列表”,仅传输未上传的分片。
- 分片合并:所有分片上传完成后,通知服务器合并分片为完整文件。
二、完整实现步骤(前端 + 后端)
1. 前端实现(核心步骤)
前端负责文件分片、记录上传状态、断点续传触发,以下是关键代码逻辑:
1.1 生成文件唯一标识(避免同文件重复上传)
常用 spark-md5 库计算文件内容的 MD5 值(大文件需分片读流计算,避免内存溢出)
import SparkMD5 from 'spark-md5';
// 计算文件MD5(分片读流,适合大文件)
function calculateFileMD5(file) {
return new Promise((resolve) => {
const chunkSize = 2 * 1024 * 1024; // 2MB/片(读流分片,非上传分片)
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
let offset = 0;
fileReader.onload = (e) => {
spark.append(e.target.result);
offset += chunkSize;
if (offset < file.size) {
readNextChunk(); // 继续读下一片
} else {
resolve(spark.end()); // 生成最终MD5(文件唯一ID)
}
};
function readNextChunk() {
const blob = file.slice(offset, offset + chunkSize);
fileReader.readAsArrayBuffer(blob);
}
readNextChunk();
});
}
1.2 分割文件为上传分片
按固定大小分割文件,生成分片列表(含分片索引、大小等信息)。
javascript
运行
// 分割文件为上传分片
function splitFileIntoChunks(file, chunkSize = 5 * 1024 * 1024) { // 5MB/上传分片
const chunks = [];
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
chunks.push({
fileChunk: file.slice(start, end), // 分片Blob
chunkIndex: i, // 分片索引(从0开始)
totalChunks: totalChunks, // 总分片数
fileId: '', // 后续赋值为文件MD5
fileName: file.name
});
}
return chunks;
}
1.3 断点续传核心逻辑(查询已传分片 + 上传未传分片)
javascript
运行
class ResumableUpload {
constructor(file, chunkSize = 5 * 1024 * 1024) {
this.file = file;
this.chunkSize = chunkSize;
this.fileId = ''; // 文件唯一ID(MD5)
this.chunks = []; // 分片列表
this.uploadedChunks = new Set(); // 已上传分片索引(避免重复传)
}
// 初始化:计算MD5+查询已传分片
async init() {
this.fileId = await calculateFileMD5(this.file); // 生成文件唯一ID
this.chunks = splitFileIntoChunks(this.file, this.chunkSize);
// 向服务器查询:该文件已上传的分片索引
const uploadedIndices = await this.getUploadedChunksFromServer();
this.uploadedChunks = new Set(uploadedIndices);
return this;
}
// 向服务器查询已上传分片(后端接口需实现)
async getUploadedChunksFromServer() {
const response = await fetch(`/api/upload/check?fileId=${this.fileId}`, {
method: 'GET'
});
const data = await response.json();
return data.uploadedIndices || []; // 后端返回已传分片索引数组(如[0,1,3])
}
// 上传单个分片(带进度)
async uploadChunk(chunk) {
const formData = new FormData();
formData.append('fileChunk', chunk.fileChunk);
formData.append('fileId', this.fileId);
formData.append('chunkIndex', chunk.chunkIndex);
formData.append('totalChunks', chunk.totalChunks);
formData.append('fileName', chunk.fileName);
return fetch('/api/upload/chunk', {
method: 'POST',
body: formData,
// 可选:监听分片上传进度
onProgress: (e) => {
if (e.lengthComputable) {
const progress = (e.loaded / e.total) * 100;
console.log(`分片${chunk.chunkIndex}上传进度:${progress.toFixed(2)}%`);
}
}
});
}
// 批量上传未传分片(可控制并发数,避免请求过多)
async startUpload(concurrent = 3) {
// 筛选未上传的分片
const unUploadedChunks = this.chunks.filter(
chunk => !this.uploadedChunks.has(chunk.chunkIndex)
);
// 控制并发上传(用Promise.allSettled避免单个失败阻断整体)
const pool = [];
for (const chunk of unUploadedChunks) {
const task = this.uploadChunk(chunk)
.then(() => {
this.uploadedChunks.add(chunk.chunkIndex); // 标记为已上传
console.log(`分片${chunk.chunkIndex}上传成功`);
})
.catch((err) => {
console.error(`分片${chunk.chunkIndex}上传失败:`, err);
// 可选:失败重试逻辑(如重试3次)
});
pool.push(task);
// 控制并发数:当池内任务数达到concurrent,等待一个完成再继续
if (pool.length >= concurrent) {
await Promise.race(pool);
pool.splice(pool.findIndex(t => t.isSettled), 1); // 移除已完成的任务
}
}
// 等待所有分片上传完成
await Promise.allSettled(pool);
// 检查是否所有分片都上传完成
if (this.uploadedChunks.size === this.chunks.length) {
await this.requestMerge(); // 通知服务器合并分片
console.log('所有分片上传完成,已请求服务器合并文件');
} else {
throw new Error('部分分片上传失败,请重试');
}
}
// 通知服务器合并分片(后端接口需实现)
async requestMerge() {
await fetch('/api/upload/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileId: this.fileId,
totalChunks: this.chunks.length,
fileName: this.file.name
})
});
}
// 暂停上传(前端层面:停止发送新的分片请求,已发送的无法中断)
pauseUpload() {
// 实际项目中可通过“取消请求”实现(如AbortController)
this.isPaused = true;
console.log('上传已暂停');
}
}
1.4 前端使用示例
javascript
运行
// 监听文件选择
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file || file.size < 1024 * 1024) return; // 仅处理大文件(如>1MB)
// 初始化断点续传实例
const uploader = await new ResumableUpload(file, 5 * 1024 * 1024).init();
console.log(`文件${file.name}初始化完成,共${uploader.chunks.length}个分片,已上传${uploader.uploadedChunks.size}个`);
// 开始上传(并发3个分片)
document.getElementById('startBtn').addEventListener('click', () => {
uploader.startUpload(3);
});
// 暂停上传
document.getElementById('pauseBtn').addEventListener('click', () => {
uploader.pauseUpload();
});
});
2. 后端实现(核心逻辑)
后端负责接收分片、存储已传分片、校验分片完整性、合并分片,以 Node.js(Express)为例:
2.1 存储设计
- 为每个文件创建独立文件夹(以
fileId命名),用于存储该文件的所有分片。 - 记录已传分片的索引(可存在文件夹内的
manifest.json,或数据库中)。
2.2 关键接口实现
javascript
运行
const express = require('express');
const multer = require('multer');
const fs = require('fs-extra');
const path = require('path');
const app = express();
// 配置存储:分片临时存储路径(如 ./uploads/chunks/[fileId]/[chunkIndex])
const chunkStorage = multer.diskStorage({
destination: (req, file, cb) => {
const fileId = req.body.fileId;
const chunkDir = path.join(__dirname, 'uploads', 'chunks', fileId);
fs.ensureDirSync(chunkDir); // 确保文件夹存在
cb(null, chunkDir);
},
filename: (req, file, cb) => {
const chunkIndex = req.body.chunkIndex;
cb(null, `${chunkIndex}`); // 分片文件名直接用索引(如 0、1、2)
}
});
const uploadChunk = multer({ storage: chunkStorage }).single('fileChunk');
// 1. 接口1:查询文件已上传的分片
app.get('/api/upload/check', async (req, res) => {
const { fileId } = req.query;
const chunkDir = path.join(__dirname, 'uploads', 'chunks', fileId);
if (!fs.existsSync(chunkDir)) {
return res.json({ uploadedIndices: [] }); // 文件夹不存在,无已传分片
}
// 读取文件夹内的分片文件,获取已传索引
const chunkFiles = fs.readdirSync(chunkDir);
const uploadedIndices = chunkFiles.map(file => parseInt(file, 10)).filter(idx => !isNaN(idx));
res.json({ uploadedIndices });
});
// 2. 接口2:接收分片
app.post('/api/upload/chunk', uploadChunk, (req, res) => {
// multer已自动保存分片到指定路径
res.json({ code: 0, msg: '分片上传成功' });
});
// 3. 接口3:合并分片
app.post('/api/upload/merge', async (req, res) => {
const { fileId, totalChunks, fileName } = req.body;
const chunkDir = path.join(__dirname, 'uploads', 'chunks', fileId);
const targetFile = path.join(__dirname, 'uploads', 'completed', fileName); // 最终文件路径
try {
// 1. 检查所有分片是否齐全
const chunkFiles = fs.readdirSync(chunkDir);
if (chunkFiles.length !== totalChunks) {
return res.status(400).json({ code: 1, msg: '分片不完整,无法合并' });
}
// 2. 按索引顺序合并分片(创建可写流,依次追加分片)
const writeStream = fs.createWriteStream(targetFile);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(chunkDir, `${i}`);
const readStream = fs.createReadStream(chunkPath);
// 同步追加分片(确保顺序)
await new Promise((resolve) => {
readStream.pipe(writeStream, { end: false }); // end:false 不关闭写流
readStream.on('end', resolve);
});
}
// 所有分片追加完成,关闭写流
writeStream.end();
// 3. 合并完成后,删除临时分片文件夹
fs.removeSync(chunkDir);
res.json({ code: 0, msg: '文件合并成功', fileUrl: `/uploads/completed/${fileName}` });
} catch (err) {
res.status(500).json({ code: 2, msg: '合并失败', err: err.message });
}
});
app.listen(3000, () => console.log('服务器启动:http://localhost:3000'));
三、关键优化点
- 分片大小选择:过小(如 1MB)会导致请求数过多,过大(如 50MB)会增加单次传输失败概率,推荐 5MB~20MB(根据业务网络环境调整)。
- 并发控制:前端上传分片时控制并发数(如 3~5 个),避免浏览器请求队列阻塞;后端可限制单个文件的并发上传数,防止资源占用过高。
- 断点续传持久化:前端可将
fileId和uploadedChunks存储到localStorage,即使页面刷新,重新初始化时仍能读取已上传状态。 - 分片校验:上传分片时可携带分片的 MD5(前端计算分片 MD5),后端接收后校验,避免分片损坏导致合并后文件错误。
- 过期清理:后端定期清理长期未完成合并的分片文件夹(如超过 7 天),避免磁盘空间浪费。
四、总结
断点续传的核心是 “分片传输 + 状态记录”:
- 前端负责文件分片、查询已传状态、并发上传未传分片;
- 后端负责接收分片、存储状态、合并分片;
- 通过文件唯一 ID(如 MD5)关联分片,通过已传分片索引实现 “断点”,避免重复传输。
6、如何做懒加载
懒加载(Lazy Loading)是一种优化网页性能的技术,核心思想是只加载用户当前可视区域内的资源(如图片、视频),当用户滚动到对应区域时再加载其他资源,从而减少初始加载时间和带宽消耗。
以下是懒加载的实现方案,主要以图片为例(视频等资源原理类似):
一、核心原理
- 占位符:初始时不设置资源的真实地址(如图片的
src),而是用占位符(如低质量缩略图、灰色背景)。 - 监听滚动:通过监听页面滚动事件,判断元素是否进入可视区域。
- 动态加载:当元素进入可视区域时,再设置真实资源地址(如将
data-src的值赋给src),触发加载。
二、实现方式
1. 原生 JavaScript 实现(基础版)
适合简单场景,手动监听滚动和判断元素位置:
html
预览
<!-- 图片用data-src存储真实地址,src用占位符 -->
<img class="lazy" data-src="image1.jpg" src="placeholder.png" alt="示例图片">
<img class="lazy" data-src="image2.jpg" src="placeholder.png" alt="示例图片">
<img class="lazy" data-src="image3.jpg" src="placeholder.png" alt="示例图片">
<!-- 更多图片... -->
<script>
// 获取所有需要懒加载的元素
const lazyImages = document.querySelectorAll('img.lazy');
// 判断元素是否在可视区域内
function isInViewport(el) {
const rect = el.getBoundingClientRect();
// 元素顶部 <= 视口高度(可见),且元素底部 >= 0(未完全滚出)
return (
rect.top <= (window.innerHeight || document.documentElement.clientHeight) + 100 && // +100提前加载
rect.bottom >= 0
);
}
// 加载可视区域内的图片
function loadVisibleImages() {
lazyImages.forEach(img => {
if (isInViewport(img) && !img.src.includes(img.dataset.src)) {
img.src = img.dataset.src; // 赋值真实地址,触发加载
img.classList.remove('lazy'); // 移除懒加载类,避免重复处理
}
});
}
// 初始加载一次可视区域图片
loadVisibleImages();
// 监听滚动事件,动态加载
window.addEventListener('scroll', loadVisibleImages);
// 监听窗口大小变化(如旋转屏幕)
window.addEventListener('resize', loadVisibleImages);
// 监听页面刷新/加载完成
window.addEventListener('load', loadVisibleImages);
</script>
关键点:
- 用
data-src存储真实图片地址,避免初始加载; isInViewport函数通过getBoundingClientRect()判断元素位置;- 提前加载(
+100):当元素距离可视区域还有 100px 时就开始加载,提升用户体验。
2. 使用 IntersectionObserver API(现代浏览器推荐)
IntersectionObserver是浏览器原生 API,可自动监听元素是否进入可视区域,性能更优(无需频繁计算滚动事件):
html
预览
<img class="lazy" data-src="image1.jpg" src="placeholder.png" alt="示例图片">
<img class="lazy" data-src="image2.jpg" src="placeholder.png" alt="示例图片">
<script>
// 实例化观察者,配置回调函数
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// 当元素进入可视区域
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 加载真实图片
observer.unobserve(img); // 停止观察该元素
img.classList.remove('lazy');
}
});
}, {
rootMargin: '100px 0px' // 提前100px开始监听(类似提前加载)
});
// 观察所有懒加载图片
document.querySelectorAll('img.lazy').forEach(img => {
observer.observe(img);
});
</script>
优势:
- 性能更好:浏览器原生优化,避免滚动事件的高频触发;
- 配置灵活:通过
rootMargin设置提前加载区域,threshold设置可见比例阈值。 - 兼容性:现代浏览器(Chrome 51+、Firefox 55+、Edge 15+)支持,低版本需 polyfill。
3. 第三方库(成熟方案)
对于复杂场景(如列表、动态加载内容),可使用成熟库简化开发:
-
lazysizes:轻量级(~4KB),支持图片、视频、iframe,自动处理响应式图片(
srcset)。html
预览
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js"></script> <img class="lazyload" data-src="image1.jpg" src="placeholder.png" alt="">只需添加
lazyload类和data-src,库会自动处理懒加载。 -
lozad.js:基于
IntersectionObserver,体积更小(~1KB),无依赖。html
预览
<script src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js"></script> <script> const observer = lozad('.lazy'); // 选择器 observer.observe(); </script> <img class="lazy" data-src="image1.jpg" src="placeholder.png" alt="">
三、优化与注意事项
-
占位符设计:用与目标图片同尺寸的占位符(如
1x1像素的透明图拉伸),避免页面布局抖动(CLS:累积布局偏移)。 -
响应式图片:结合
data-srcset和sizes实现响应式懒加载:html
预览
<img class="lazy" data-srcset="small.jpg 400w, large.jpg 800w" data-sizes="(max-width: 600px) 400px, 800px" src="placeholder.png">加载时将
data-srcset赋值给srcset。 -
SEO 友好:对搜索引擎爬虫重要的图片(如首屏关键图)不使用懒加载,避免爬虫无法抓取。
-
视频 /iframe 懒加载:原理类似,视频可用
data-src存储src,iframe 同理:html
预览
<video class="lazy" data-src="video.mp4" poster="poster.jpg" controls></video> <iframe class="lazy" data-src="page.html"></iframe> -
禁用浏览器预加载:对懒加载图片添加
loading="lazy"(浏览器原生懒加载属性)作为补充,但仍需兼容处理:html
预览
<img class="lazy" data-src="image.jpg" loading="lazy" alt="">
四、总结
- 简单场景:用
IntersectionObserver实现,性能优、代码简洁。 - 兼容旧浏览器:用滚动监听 +
getBoundingClientRect()作为降级方案。 - 复杂项目:直接使用
lazysizes等成熟库,减少重复开发。
懒加载的核心是 “按需加载”,合理使用可显著提升长列表、图片密集型页面的加载速度。
7、css如何实现等腰三角形,直角三角形
在 CSS 中实现三角形主要利用边框(border)的特性:当元素宽高为 0 时,边框会呈现三角形拼接的效果,通过隐藏部分边框即可得到不同形状的三角形。以下是等腰三角形和直角三角形的具体实现方法:
一、等腰三角形(以向上的等腰三角形为例)
原理:
- 设置元素宽高为 0,通过调整四个方向的边框宽度和颜色,隐藏其中三个方向的边框(颜色设为透明),剩余一个方向的边框会形成等腰三角形。
代码:
css
/* 向上的等腰三角形 */
.triangle-up {
width: 0;
height: 0;
/* 左右边框透明,下边框为三角形颜色,边框宽度决定三角形大小 */
border-left: 50px solid transparent; /* 左边框宽度 = 三角形底边长的一半 */
border-right: 50px solid transparent; /* 右边框宽度 = 三角形底边长的一半 */
border-bottom: 100px solid #ff0000; /* 下边框高度 = 三角形的高 */
}
效果:
- 底边长度 = 左边框宽度 + 右边框宽度(此处
50px + 50px = 100px)。 - 三角形高度 = 下边框宽度(此处
100px)。
方向扩展:
-
向下的等腰三角形:保留上边框,隐藏其他边框:
css
.triangle-down { width: 0; height: 0; border-left: 50px solid transparent; border-right: 50px solid transparent; border-top: 100px solid #00ff00; /* 上边框为颜色 */ } -
向左的等腰三角形:保留右边框,隐藏其他边框:
css
.triangle-left { width: 0; height: 0; border-top: 50px solid transparent; border-bottom: 50px solid transparent; border-right: 100px solid #0000ff; /* 右边框为颜色 */ } -
向右的等腰三角形:保留左边框,隐藏其他边框:
css
.triangle-right { width: 0; height: 0; border-top: 50px solid transparent; border-bottom: 50px solid transparent; border-left: 100px solid #ffff00; /* 左边框为颜色 */ }
二、直角三角形(以右下角直角为例)
原理:
- 同样设置宽高为 0,保留两个相邻方向的边框(如底部和右侧),并隐藏另外两个方向的边框,形成直角三角形。
代码:
css
/* 右下角为直角的三角形(向左上方倾斜) */
.triangle-right-angle {
width: 0;
height: 0;
border-bottom: 100px solid #ff0000; /* 下边框:高度 = 三角形的高 */
border-right: 100px solid transparent; /* 右边框:宽度 = 三角形的底 */
/* 隐藏上、左边框(默认透明,可省略) */
}
效果:
- 直角边分别由下边框(高度)和右边框(宽度)决定,此处形成直角边为
100px的等腰直角三角形。
方向扩展:
-
左上角为直角:保留上边框和左边框:
css
.triangle-top-left { width: 0; height: 0; border-top: 100px solid #00ff00; border-left: 100px solid transparent; } -
右上角为直角:保留上边框和右边框:
css
.triangle-top-right { width: 0; height: 0; border-top: 100px solid #0000ff; border-right: 100px solid transparent; } -
左下角为直角:保留下边框和左边框:
css
.triangle-bottom-left { width: 0; height: 0; border-bottom: 100px solid #ffff00; border-left: 100px solid transparent; }
三、关键总结
-
核心依赖:利用
border的梯形拼接特性,宽高为 0 时边框会形成三角形。 -
形状控制:
- 等腰三角形:保留对边的边框(如上下或左右),且对称方向的透明边框宽度相等。
- 直角三角形:保留相邻的边框(如上下 + 左右中的一组),其中一个边框设为颜色,另一个设为透明。
-
尺寸调整:通过修改边框的
width值控制三角形的边长和高度。 -
兼容性:所有浏览器均支持,无兼容性问题。
通过调整边框的方向、宽度和颜色,可以灵活实现各种角度和大小的三角形。
8、如何实现3列布局
在 CSS 中实现 3 列布局有多种方案,适用于不同场景(如是否需要固定宽度、是否响应式等)。以下是常见的实现方式,按灵活性和兼容性排序:
一、Flexbox 布局(推荐,简单灵活)
Flexbox 是现代布局首选,代码简洁,支持灵活调整列宽和对齐方式,适合响应式场景。
代码:
html
预览
<div class="container">
<div class="column">列1</div>
<div class="column">列2</div>
<div class="column">列3</div>
</div>
<style>
.container {
display: flex; /* 启用 Flex 布局 */
width: 100%; /* 容器宽度(可按需设置) */
gap: 20px; /* 列之间的间距(可选) */
}
.column {
flex: 1; /* 三列平均分配宽度 */
height: 300px; /* 列高度(按需设置) */
background: #f0f0f0;
padding: 20px;
}
/* 如需设置固定宽度的列(如中间列固定) */
/* .column:nth-child(2) {
flex: none;
width: 300px;
} */
</style>
特点:
- 三列默认平均分配容器宽度(
flex:1表示占比相同)。 - 支持设置某列固定宽度(如中间列固定,两侧自适应)。
- 响应式友好:可通过
@media查询在小屏幕下改为堆叠布局。
二、Grid 布局(现代方案,功能强大)
Grid 布局专为二维布局设计,适合复杂网格场景,代码更直观。
代码:
html
预览
<div class="container">
<div class="column">列1</div>
<div class="column">列2</div>
<div class="column">列3</div>
</div>
<style>
.container {
display: grid; /* 启用 Grid 布局 */
grid-template-columns: 1fr 1fr 1fr; /* 三列平均分配(1fr 表示一份) */
gap: 20px; /* 列间距 */
width: 100%;
}
.column {
height: 300px;
background: #f0f0f0;
padding: 20px;
}
/* 如需固定列宽(如左右200px,中间自适应) */
/* .container {
grid-template-columns: 200px 1fr 200px;
} */
</style>
特点:
- 通过
grid-template-columns直接定义列宽,语义清晰。 - 支持混合固定宽度和自适应宽度(如
200px 1fr 200px)。 - 兼容性:IE11 部分支持,现代浏览器(Chrome、Firefox 等)完全支持。
三、浮动布局(兼容旧浏览器)
通过 float 属性实现,需处理浮动清除问题,适合兼容 IE 等旧浏览器。
代码:
html
预览
<div class="container">
<div class="column left">列1</div>
<div class="column right">列3</div>
<div class="column center">列2</div> <!-- 注意顺序:中间列放最后 -->
<div class="clear"></div> <!-- 清除浮动 -->
</div>
<style>
.container {
width: 100%;
overflow: hidden; /* 或用 clear 类清除浮动 */
}
.column {
height: 300px;
padding: 20px;
background: #f0f0f0;
}
/* 左右列固定宽度,中间列自适应 */
.left, .right {
width: 200px;
float: left;
}
.right {
float: right; /* 右列靠右浮动 */
}
.center {
margin: 0 220px; /* 左右margin等于左右列宽度+间距(200+20) */
}
.clear {
clear: both; /* 清除浮动,避免容器高度塌陷 */
}
</style>
特点:
- 需注意 HTML 结构顺序(中间列放最后)。
- 需手动清除浮动(
clear:both或overflow:hidden)。 - 适合需要兼容 IE8 及以下的场景,现代项目不推荐。
四、响应式适配(小屏幕堆叠)
在移动设备上,3 列布局通常需要改为单列堆叠,以保证可读性。以 Flexbox 为例:
css
/* 屏幕宽度小于 768px 时改为单列 */
@media (max-width: 768px) {
.container {
flex-direction: column; /* 垂直排列 */
gap: 10px;
}
.column {
height: auto; /* 高度自适应内容 */
}
}
总结
- 首选方案:Flexbox(简单灵活,适合大多数场景)或 Grid(复杂布局更直观)。
- 兼容旧浏览器:浮动布局(需处理清除浮动)。
- 响应式:结合
@media查询,在小屏幕下转为单列布局。
9、node如何实现登录功能
Node.js 实现登录功能通常涉及 用户认证(验证账号密码)、会话管理(保持登录状态)和 安全处理(密码加密、防止攻击)。以下是基于 Express 框架的完整实现方案,包含核心流程和关键代码:
一、核心技术栈
- Web 框架:Express(处理 HTTP 请求)
- 数据库:MongoDB + Mongoose(存储用户信息,也可替换为 MySQL 等)
- 密码加密:bcrypt(不可逆加密,安全存储密码)
- 会话管理:JWT(无状态令牌,适合前后端分离)或 express-session(基于 cookie 的会话)
- 请求验证:express-validator(验证登录参数)
10、service worker做离线应用
Service Worker 是浏览器在后台运行的独立线程,可实现离线缓存、拦截网络请求等功能,是构建离线应用的核心技术。以下是利用 Service Worker 实现离线应用的完整方案:
一、核心原理
- 安装(Install) :Service Worker 注册后,会触发
install事件,此时可缓存静态资源(HTML、CSS、JS、图片等)。 - 激活(Activate) :旧的 Service Worker 被替换时触发,可清理旧缓存。
- 拦截请求(Fetch) :页面所有网络请求会经过 Service Worker,可优先返回缓存内容(离线时),或请求网络并更新缓存(在线时)。
二、实现步骤
1. 注册 Service Worker(前端页面)
在主页面(如 index.html)中注册 Service Worker,使其生效:
html
预览
<!-- index.html -->
<script>
// 检查浏览器是否支持 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
// 注册 Service Worker 文件(sw.js)
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker 注册成功:', registration.scope);
} catch (err) {
console.error('Service Worker 注册失败:', err);
}
});
}
</script>
2. 编写 Service Worker 核心逻辑(sw.js)
创建 sw.js 文件,处理安装、激活、请求拦截等事件:
javascript
运行
// 缓存名称(版本号,用于更新缓存)
const CACHE_NAME = 'offline-cache-v1';
// 需要缓存的静态资源列表
const ASSETS_TO_CACHE = [
'/', // 首页HTML
'/index.html',
'/styles.css',
'/app.js',
'/logo.png',
'/fallback.html' // 离线时的备用页面
];
// 1. 安装阶段:缓存静态资源
self.addEventListener('install', (event) => {
// 等待缓存完成后再完成安装
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('缓存静态资源:', ASSETS_TO_CACHE);
return cache.addAll(ASSETS_TO_CACHE); // 缓存所有指定资源
})
.then(() => self.skipWaiting()) // 跳过等待,立即激活新的 Service Worker
);
});
// 2. 激活阶段:清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((name) => {
// 删除非当前版本的缓存
if (name !== CACHE_NAME) {
console.log('删除旧缓存:', name);
return caches.delete(name);
}
})
);
}).then(() => self.clients.claim()) // 立即控制所有打开的页面
);
});
// 3. 拦截请求:优先返回缓存,无缓存则请求网络
self.addEventListener('fetch', (event) => {
// 对于HTML页面,采用"网络优先,缓存兜底"策略
if (event.request.headers.get('Accept').includes('text/html')) {
event.respondWith(
fetch(event.request)
.then((response) => {
// 网络请求成功,更新缓存
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response.clone());
});
return response;
})
.catch(() => {
// 网络失败,返回缓存的备用页面
return caches.match('/fallback.html');
})
);
return;
}
// 对于其他资源(CSS、JS、图片等),采用"缓存优先,网络更新"策略
event.respondWith(
caches.match(event.request)
.then((cachedResponse) => {
// 先返回缓存内容
const fetchPromise = fetch(event.request)
.then((networkResponse) => {
// 网络请求成功,更新缓存
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
// 若有缓存则返回缓存,同时后台更新缓存;若无缓存则等待网络请求
return cachedResponse || fetchPromise;
})
);
});
3. 缓存策略说明
- 静态资源(CSS/JS/ 图片) :采用「缓存优先」,优先加载缓存内容,同时后台请求网络更新缓存,确保下次访问是最新资源。
- HTML 页面:采用「网络优先,缓存兜底」,优先请求网络获取最新内容,网络失败时返回缓存的备用页面(避免展示旧内容)。
- 动态数据:可在
fetch事件中单独处理 API 请求,例如缓存接口返回数据,离线时返回缓存数据。
4. 处理缓存更新
当静态资源更新时,需通过以下步骤让用户获取最新版本:
- 修改
sw.js中的CACHE_NAME(如从offline-cache-v1改为offline-cache-v2)。 - 浏览器会检测到
sw.js内容变化,触发重新安装。 - 新的 Service Worker 激活时,会清理旧版本缓存(通过
activate事件中的逻辑)。
5. 离线状态提示(可选)
在前端页面中监听在线 / 离线状态,提示用户:
javascript
运行
// 监听网络状态变化
function updateOnlineStatus() {
const isOnline = navigator.onLine;
const statusEl = document.getElementById('status');
statusEl.textContent = isOnline ? '在线' : '离线模式,部分功能可能受限';
statusEl.className = isOnline ? 'online' : 'offline';
}
// 初始检查
updateOnlineStatus();
// 监听状态变化
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
三、关键注意事项
- HTTPS 环境:Service Worker 仅在 HTTPS 环境下生效(本地开发
localhost除外)。 - 作用域(Scope) :Service Worker 只能控制其注册路径及其子路径下的页面(如
/sw.js可控制所有页面,/js/sw.js只能控制/js/下的页面)。 - 缓存体积:浏览器对缓存空间有限制(通常几十 MB),避免缓存过多资源。
- 调试工具:在 Chrome DevTools 的「Application > Service Workers」中可调试、更新、注销 Service Worker。
- 兼容性:现代浏览器(Chrome、Firefox、Edge、Safari 11.1+)均支持,可通过 caniuse 查看详情。
四、完整离线应用结构
plaintext
offline-app/
├── index.html # 主页面(注册 Service Worker)
├── sw.js # Service Worker 逻辑
├── styles.css # 样式表(被缓存)
├── app.js # 业务逻辑(被缓存)
├── logo.png # 图片(被缓存)
└── fallback.html # 离线备用页面
通过以上步骤,即可实现一个支持离线访问的应用:用户首次在线访问时缓存资源,后续离线状态下仍能加载已缓存的页面和资源,大幅提升弱网或无网环境下的用户体验。