拖拽过程中,如果原本的元素消失了,onDrop
还能触发么?具体表现是什么?
即使原始元素在拖拽过程中被移除了,只要拖拽操作未被中断,onDrop
事件仍然可以触发
-
拖拽数据独立存储
拖拽操作一旦开始(dragstart
),浏览器会将拖拽数据存储在独立的DataTransfer
对象中。即使原始元素被移除,已存储的拖拽数据依然有效。 -
视觉反馈与 DOM 解耦
拖拽过程中显示的「拖拽预览图像」是浏览器生成的副本,与原始元素的 DOM 状态无关。即使原始元素消失,预览图像通常仍会正常显示。
具体表现
1. onDrop
正常触发
-
如果拖拽操作成功完成(用户释放鼠标在有效的拖放目标上),
onDrop
仍会触发。 -
可以通过
event.dataTransfer.getData()
正常获取拖拽数据。 -
示例代码:
// 拖拽源元素 source.addEventListener('dragstart', (e) => {e.dataTransfer.setData('text/plain', 'Hello World');e.target.remove(); // 立即移除原始元素 });// 放置目标 target.addEventListener('drop', (e) => {e.preventDefault();console.log(e.dataTransfer.getData('text/plain')); // 输出 "Hello World" });
2. dragend
事件可能异常
-
如果原始元素被移除,其
dragend
事件可能无法触发,或触发时因元素不存在而导致关联逻辑出错。 -
示例问题:
source.addEventListener('dragend', () => {console.log('拖拽结束'); // 可能不会执行 });
3. 意外中断拖拽操作
-
如果在
dragstart
中直接移除元素,某些浏览器可能因渲染更新而意外终止拖拽操作,导致onDrop
无法触发。 - 解决方案:使用异步移除(如
setTimeout
)确保拖拽操作初始化完成:
source.addEventListener('dragstart', (e) => {e.dataTransfer.setData('text/plain', 'Hello World');setTimeout(() => e.target.remove(), 0); // 异步移除
});
总结
-
onDrop
触发条件:仅取决于用户是否在有效的目标上释放鼠标,与原始元素是否存在无关。 -
风险点:
dragend
事件可能不可靠,且直接移除元素可能导致浏览器行为不一致。 -
最佳实践:如需移除原始元素,建议在
dragend
或drop
事件中处理,而非dragstart
。
如何改变拖拽预览图?如何让拖拽预览图有圆角?
一、核心方法:setDragImage()
通过 dragstart
事件的 dataTransfer.setDragImage()
方法,可直接指定拖拽预览图。
代码示例
element.addEventListener('dragstart', (e) => {// 1. 创建一个自定义预览元素const preview = document.createElement('div');preview.textContent = "拖拽我";preview.style.cssText = `width: 100px;height: 40px;background: #2196F3;color: white;border-radius: 8px; // 关键:圆角样式display: flex;align-items: center;justify-content: center;`;// 2. 将元素临时添加到 DOM 中(部分浏览器需要渲染才能捕获图像)document.body.appendChild(preview);// 3. 设置为拖拽预览图,并指定光标偏移位置(居中)e.dataTransfer.setDragImage(preview, preview.offsetWidth / 2, preview.offsetHeight / 2);// 4. 立即移除临时元素(可选)setTimeout(() => document.body.removeChild(preview), 0);
});
二、实现圆角的注意事项
1. 必须确保元素已渲染
-
部分浏览器(如 Firefox)要求预览元素必须已插入 DOM 并完成渲染,否则无法生成图像。
-
解决方案:临时添加到 DOM 后立即移除。
2. 使用 Canvas 生成复杂预览
若需要更复杂的图形(如带阴影的圆角矩形),可通过 Canvas 生成图像:
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 40;
const ctx = canvas.getContext('2d');// 绘制圆角矩形
ctx.beginPath();
ctx.roundRect(0, 0, 100, 40, 8); // 圆角半径 8px
ctx.fillStyle = '#2196F3';
ctx.fill();// 设置为拖拽预览
e.dataTransfer.setDragImage(canvas, 50, 20); // 偏移到中心
三、浏览器兼容性
方法 | Chrome | Firefox | Safari |
---|---|---|---|
setDragImage() |
✅ | ✅ | ✅ |
Canvas 作为预览图 | ✅ | ✅ | ✅ |
未渲染元素的 setDragImage |
✅ | ❌ | ✅ |
四、完整示例(含圆角和动画)
<style>.drag-item {width: 120px;padding: 12px;background: #4CAF50;color: white;cursor: grab;}
</style><div class="drag-item" draggable="true">拖拽我</div><script>document.querySelector('.drag-item').addEventListener('dragstart', (e) => {// 创建预览元素const preview = document.createElement('div');preview.textContent = "正在拖拽...";preview.style.cssText = `width: 120px;padding: 12px;background: #4CAF50;color: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.2);opacity: 0.8;`;// 临时插入 DOMdocument.body.appendChild(preview);e.dataTransfer.setDragImage(preview, 60, 20); // 居中偏移setTimeout(() => preview.remove(), 0);});
</script>
五、高级技巧
1. 动态预览内容
preview.innerHTML = `<div style="display: flex; align-items: center;"><img src="icon.png" width="24"><span>${e.target.textContent}</span></div>
`;
2. 隐藏默认预览
e.dataTransfer.setDragImage(new Image(), 0, 0); // 设置透明图像
通过 setDragImage()
结合 CSS 或 Canvas,可以完全控制拖拽预览图的样式,包括圆角、阴影等复杂效果。
如何让 echart 的内容跟随容器大小而变化?onResize 的时候要怎么做?如果有可伸缩侧边栏之类的,导致容器因为其他原因发生了改变,应该用什么事件监听?
核心方法:resize()
+ 尺寸监听
ECharts 实例通过 resize()
方法重新计算尺寸,需在容器尺寸变化时手动调用。
一、基础场景:窗口缩放
监听浏览器窗口的 resize
事件:
const chart = echarts.init(document.getElementById('chart-container'));// 监听窗口变化
window.addEventListener('resize', () => {chart.resize();
});
二、进阶场景:容器尺寸动态变化(如侧边栏伸缩)
1. 使用 ResizeObserver
(推荐)
-
直接监听容器尺寸变化,无需依赖父级元素事件
-
代码示例:
const container = document.getElementById('chart-container'); const chart = echarts.init(container);const resizeObserver = new ResizeObserver(() => {chart.resize(); });resizeObserver.observe(container); // 开始监听// 组件卸载时销毁(重要!) // Vue: onBeforeUnmount(() => resizeObserver.disconnect()) // React: useEffect(() => () => resizeObserver.disconnect(), [])
-
兼容性处理(旧版浏览器):
npm install resize-observer-polyfill
import ResizeObserver from 'resize-observer-polyfill';
2. 框架特定方案(如侧边栏回调)
-
若使用 Element UI/ Ant Design 等组件库,在侧边栏展开/折叠的回调中触发:
// Element UI 侧边栏折叠事件 <el-menu @collapse="handleCollapse"> → handleCollapse() {this.$nextTick(() => this.chart.resize()); }
三、性能优化
1. 防抖处理(高频变化场景)
let resizeTimer;
const resizeObserver = new ResizeObserver(() => {clearTimeout(resizeTimer);resizeTimer = setTimeout(() => chart.resize(), 100);
});
2. 容器动画同步
.chart-container {transition: width 0.3s; /* 动画持续期间持续触发 ResizeObserver */
}
四、完整示例(Vue3 + TypeScript)
<template><div ref="chartContainer" class="chart"></div>
</template><script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue';
import * as echarts from 'echarts';
import ResizeObserver from 'resize-observer-polyfill';const chartContainer = ref<HTMLElement>();
let chart: echarts.ECharts;
let resizeObserver: ResizeObserver;onMounted(() => {chart = echarts.init(chartContainer.value!);chart.setOption({ /* 配置项 */ });// 监听容器变化resizeObserver = new ResizeObserver(() => chart.resize());resizeObserver.observe(chartContainer.value!);
});onBeforeUnmount(() => {resizeObserver?.disconnect();chart?.dispose();
});
</script>
五、常见问题排查
现象 | 解决方案 |
---|---|
图表未响应侧边栏变化 | 确认监听的是图表容器而非父元素 |
出现空白间隙 | 检查容器 CSS 是否设置 width: 100% |
内存泄漏 | 确保组件卸载时调用 disconnect() |
通过 ResizeObserver
+ resize()
的组合,可精准响应任意原因导致的容器尺寸变化,无需依赖具体 UI 框架。
如果是 toc 产品,后端为 index.html 设置了 1 h 的 max age,请问从你重新构建代码发布,大概多久之后,所有用户都可以看到新界面?如果用户访问出现了白屏,是什么原因?
问题一:代码发布后用户看到新界面的时间
核心时间范围:最长 1 小时,但存在变量
-
理论最长时间
由于index.html
设置了max-age=3600
(1小时),未主动刷新页面的用户需等待缓存过期后才会获取新版本。-
用户首次访问时间点不同:若用户恰好在发布前 5 分钟访问过,需等待 55 分钟才能更新。
-
-
实际可能更短
-
强制刷新(Ctrl+F5):用户手动刷新会跳过缓存,立即获取新版本。
-
文件名哈希策略:若 JS/CSS 文件使用哈希指纹(如
app.a1b2c3.js
),新版本会触发浏览器重新下载资源,即使index.html
仍被缓存,也可能部分更新。 -
CDN 缓存行为:若 CDN 配置的缓存时间短于 1 小时,用户可能提前获取新版本。
-
问题二:用户访问白屏的可能原因
核心原因排查清单
原因分类 | 具体场景 | 解决方案 |
---|---|---|
缓存冲突 | 旧版 index.html 引用了已被删除或重命名的资源文件(如未哈希的 JS/CSS) |
使用内容哈希文件名,确保新旧资源共存 |
资源加载失败 | 新版本资源未正确部署到服务器,导致 404 错误 | 检查构建产物是否完整上传,验证 CDN/服务器路径 |
代码执行错误 | 新版 JS 中存在语法错误或依赖兼容性问题(如浏览器不支持 ES6+ 语法) | 启用 Babel 转译,添加错误监控(如 Sentry)捕获运行时错误 |
路由配置问题 | 单页应用(SPA)路由未正确配置,导致空白路由匹配 | 检查路由兜底设置(如 404 重定向到首页) |
Service Worker | 旧版 Service Worker 强制缓存了过时资源 | 更新 Service Worker 版本并触发立即激活(如 self.skipWaiting() ) |
最佳实践建议
-
缓存策略优化
-
对
index.html
设置较短缓存(如max-age=300
),或使用no-cache
配合 ETag 验证。 -
静态资源(JS/CSS/图片):设置长期缓存(如
max-age=31536000
)并添加哈希指纹。
-
-
部署流程增强
# 示例:使用 Webpack 生成带哈希的文件名 output: {filename: '[name].[contenthash].js',chunkFilename: '[name].[contenthash].chunk.js' }
-
先上传新资源,再更新
index.html
,避免出现中间状态。
-
-
监控与回滚
-
部署后实时监控错误率(如通过 APM 工具)。
-
准备快速回滚方案(如保留前一次构建产物)。
-
-
用户提示机制
// 检测版本更新并提示用户刷新 if (navigator.serviceWorker) {navigator.serviceWorker.addEventListener('controllerchange', () => {window.location.reload();}); }
示例:强制缓存失效方案
通过修改 index.html
的 URL 路径(如添加版本号)绕过缓存:
# Nginx 配置示例
location / {if ($request_uri ~* "index\.html$") {add_header Cache-Control "no-cache, must-revalidate";}
}
或使用查询参数(不推荐,部分 CDN 会忽略):
<script src="/app.js?v=20231001"></script>
toc 产品大量使用 cdn,请问 cdn 的定价大概多少?针对这样的定价策略,前端应该进行什么样的优化?前端应用中?上传贵还是下载贵?上传快还是下载快?
一、CDN 定价模型(以主流厂商为例)
CDN 的定价通常由 流量、请求次数、存储 和 增值功能 四部分构成,以下为典型价格范围(以中国大陆和全球主流 CDN 服务商为例):
计费项 | 价格范围 | 示例场景 |
---|---|---|
流量费用 | - 中国大陆: 0.1~0.3 元/GB - 海外: 0.05~~~~~~~~~~~~~~~~~~~0.2 元/GB |
用户下载 1GB JS/CSS/图片资源,约消耗 0.2 元(按国内均价) |
HTTP 请求次数 | - 0.01~0.1 元/万次 | 100 万次图片请求 ≈ 5~10 元 |
存储费用 | - 0.1~0.3 元/GB/月 | 存储 100GB 静态资源 ≈ 10~30 元/月 |
增值服务 | - DDoS 防护: 100~500 元/月 - 全站加速(动态请求): 0.3~~~~~~~~~~~~~~~~~~~~~~1 元/GB |
动态 API 加速 100GB ≈ 30~100 元 |
主流厂商参考:
-
阿里云/腾讯云:流量阶梯计价(用量越大单价越低)
-
Cloudflare:免费套餐含基础防护,Pro 套餐 20 美元/月(不限流量)
-
AWS CloudFront:按区域定价(北美 0.085 美元/GB,亚洲 0.14 美元/GB)
二、前端优化策略(降低成本 + 提升性能)
针对 CDN 定价模型,前端需重点优化 流量消耗 和 请求次数:
1. 减少流量消耗
方法 | 效果 | 实施示例 |
---|---|---|
资源压缩 | 减少 50%~70% 体积 | 使用 Brotli(最高压缩率)替代 Gzip,Webpack 配置 compression-webpack-plugin |
图片优化 | WebP 比 JPEG 节省 30%+ | <picture> 标签兼容性兜底:<source srcset="img.webp" type="image/webp"> |
代码拆分(Code Splitting) | 按需加载减少初始下载量 | Vue/React 使用动态导入:() => import('./Component') |
Tree Shaking | 移除未使用代码 | Webpack/Rollup 默认支持,确保 sideEffects: false |
2. 降低请求次数
方法 | 效果 | 实施示例 |
---|---|---|
HTTP/2 多路复用 | 单连接并行传输,减少队头阻塞 | Nginx 配置 listen 443 http2 |
资源合并 | 减少小文件请求 | 合并图标为 SVG Sprite(如 icon-sprite.svg#home ) |
合理缓存策略 | 减少重复请求 | 静态资源设置 Cache-Control: public, max-age=31536000, immutable |
CDN 边缘计算 | 请求在边缘节点处理(如压缩、重定向) | Cloudflare Workers 实现 A/B 测试分流 |
3. 监控与分析
工具 | 用途 |
---|---|
Google Lighthouse | 性能评分 + 优化建议 |
CDN 自带监控 | 分析流量热点、异常请求(如 404 资源) |
Sentry | 捕获前端错误,定位资源加载失败问题 |
三、上传 vs 下载:成本与速度对比
1. 成本对比
方向 | 定价逻辑 | 成本 |
---|---|---|
上传(写入CDN) | 通常免费或极低费用(厂商希望吸引内容注入) | 低(接近0) |
下载(用户访问) | 主要收费项(占 CDN 厂商带宽成本的核心) | 高(主要支出) |
2. 速度对比
方向 | 速度表现 |
---|---|
上传 | 依赖本地网络上行带宽(家用宽带通常 10~50Mbps),但可通过 分片上传 或 CDN 就近接入点 优化 |
下载 | 通过 CDN 全球加速节点,用户从最近节点获取资源,速度更快(通常 100Mbps+) |
四、完整优化案例
场景:一个全球化的电商网站,图片资源占比 70%,JS/CSS 资源频繁更新。
优化步骤:
图片处理:
-
使用
sharp
库批量转换为 WebP 格式 -
响应式图片:
srcset
按设备分辨率分发
<img src="product.jpg" srcset="product-400.webp 400w, product-800.webp 800w"sizes="(max-width: 600px) 400px, 800px">
-
代码优化:
-
启用 Brotli 压缩(Nginx 配置
brotli on;
) -
非核心代码动态加载:
// 点击时加载支付模块 button.addEventListener('click', () => {import('./payment.js').then(module => module.process()); });
-
-
缓存策略:
-
静态资源:
Cache-Control: public, max-age=31536000, immutable
-
HTML 文件:
Cache-Control: no-cache
(通过 ETag 验证更新)
-
五、成本估算(示例)
假设某应用月消耗:
-
流量:100TB(下载)
-
请求次数:10 亿次
-
存储:1TB
费用计算(以阿里云为例):
-
流量费:100,000 GB × 0.18 元/GB ≈ 18,000 元
-
请求费:10 亿次 ÷ 10,000 × 0.06 元 ≈ 6,000 元
-
存储费:1TB × 0.15 元/GB ≈ 150 元
-
总成本 ≈ 24,150 元/月
优化后(预计节省 40%+):
-
WebP 图片减少 30% 流量 → 流量费降至 12,600 元
-
Brotli 压缩减少 20% 请求量 → 请求费降至 4,800 元
-
总成本 ≈ 17,550 元/月
通过前端优化可直接降低 CDN 的核心成本项(流量和请求),同时提升用户体验,形成双赢局面。
图片设置协商缓存后,浏览器会整体缓存,视频能设置协商缓存么?视频的http返回内容与图片有什么区别?如何降低视频展示的成本?
一、视频能否设置协商缓存?
可以,视频与图片的缓存机制原理相同,均可通过 HTTP 缓存头实现协商缓存。
配置示例(Nginx):
location /videos/ {# 设置强缓存(可选)add_header Cache-Control "public, max-age=3600";# 协商缓存:通过 Last-Modified/ETag 验证if_modified_since before;etag on;
}
协商缓存流程:
-
首次请求:返回
200 OK
+Last-Modified
/ETag
-
再次请求:浏览器携带
If-Modified-Since
或If-None-Match
-
资源未修改:服务器返回
304 Not Modified
,浏览器使用本地缓存
二、视频与图片的 HTTP 响应差异
核心区别点:
特征 | 图片(如 JPEG) | 视频(如 MP4) |
---|---|---|
状态码 | 通常 200 OK |
可能 206 Partial Content (范围请求) |
Content-Type | image/jpeg |
video/mp4 |
Accept-Ranges | 通常无(小文件不分片) | bytes (支持分段加载) |
Content-Range | 无 | bytes 0-999/2000 (指示当前传输的字节范围) |
缓存行为 | 完整缓存 | 可能分片缓存(取决于是否启用范围请求) |
示例视频请求响应头:
HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Accept-Ranges: bytes
Content-Range: bytes 0-1048575/5242880
Content-Length: 1048576
Cache-Control: public, max-age=3600
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
三、降低视频展示成本的几种策略
1. 启用 HTTP 范围请求(Range Requests)
-
原理:允许用户仅加载观看的部分视频片段,减少无效流量消耗。
-
实现:确保服务器支持
Accept-Ranges: bytes
(Nginx 默认开启)。
2. 转码为高效编码格式
-
推荐格式:H.265(HEVC)比 H.264 节省 50% 带宽,AV1 更优但兼容性差。
-
工具示例:
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output-hevc.mp4
3. 自适应码率(ABR)
-
技术方案:使用 HLS 或 DASH 协议,根据网络状况动态切换分辨率。
-
实现步骤:
-
将视频切片(如 10s/段)并生成多码率版本
-
通过
.m3u8
(HLS)或.mpd
(DASH)描述文件管理播放
-
4. CDN 分层存储
-
策略:
-
热数据:使用 SSD 边缘节点加速
-
冷数据:存储到低价对象存储(如 AWS S3 Glacier)
-