阅读视图

发现新文章,点击刷新页面。

为什么给<a>标签设置了download属性, 浏览器没有下载而是打开新标签!!

<a>标签设置了download属性, 浏览器没有下载而是打开新标签,这个问题通常有以下几个原因:

1. 同源策略限制

download 属性只在同源 URL 或 blob/data URL 上有效:

<!-- 同源文件 - 可以下载 -->
<a href="/files/document.pdf" download>下载PDF</a>

<!-- 跨域文件 - 可能在新标签打开 -->
<a href="https://other-domain.com/file.pdf" download>可能不会下载</a>

2. 服务器 MIME 类型设置

检查服务器返回的 Content-Disposition 头:

<!-- 即使设置了download,如果服务器返回的是可预览类型,浏览器可能选择打开 -->
<a href="image.png" download>点击测试</a>

3. 浏览器兼容性

某些浏览器对 download 属性的支持有限制。

解决方案

方案1:使用同源文件

<!-- 确保文件在同一域名下 -->
<a href="/your-file.pdf" download="filename.pdf">下载文件</a>

方案2:通过 JavaScript 处理跨域下载

javascript

// 使用 fetch + blob 方式下载
async function downloadFile(url, filename) {
    try {
        const response = await fetch(url);
        const blob = await response.blob();
        const blobUrl = URL.createObjectURL(blob);
        
        const a = document.createElement('a');
        a.href = blobUrl;
        a.download = filename || 'download';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(blobUrl);
    } catch (error) {
        console.error('下载失败:', error);
    }
}

// 使用示例
downloadFile('https://example.com/file.pdf', 'my-file.pdf');

方案3:服务器端设置响应头

Content-Disposition: attachment; filename="file.pdf"
Content-Type: application/octet-stream

方案4:检查实际代码

<!-- 正确的用法 -->
<a href="file.pdf" download="自定义文件名.pdf">下载</a>

<!-- 可能有问题的情况 -->
<a href="https://其他网站.com/file.pdf" download>可能不会下载</a>
<a href="#" download>缺少href或href无效</a>

调试步骤

  1. 检查浏览器控制台是否有错误信息
  2. 查看网络面板确认文件请求状态
  3. 检查响应头中的 Content-Disposition
  4. 测试不同浏览器看是否是兼容性问题

个人推荐 一般情况下使用方案一

<!-- 假如你的文件名是https://other-domain.com/file.pdf 直接省略域名使用下面的写法-->
<a href="/files/document.pdf" download>下载PDF</a>

用HTML5 构建一个敲击乐钢琴

用HTML5 构建一个敲击乐钢琴 在前端开发的世界里,每一个交互项目都是技术与创意的结合体。HTML5 敲击乐作为一款轻量且有趣的 Web 应用,不仅能让用户通过键盘体验音乐的乐趣,更集中体现了前端

HTML5 敲击乐:模块化开发打造交互式钢琴应用的实践指南

HTML5 敲击乐:模块化开发打造交互式钢琴应用的实践指南

在前端开发领域,HTML5 技术的崛起为交互式应用开发提供了无限可能,其中 “敲击乐” 模拟钢琴项目便是融合前端三剑客技术、践行模块化开发思想的典型案例。作为直接面向用户的 “体验导演”,JS 开发者通过 HTML 构建结构、CSS 美化样式、JavaScript 实现交互,将一行行代码转化为可触摸、可聆听的虚拟乐器,完美诠释了前端技术 “即见即所得” 的独特魅力。本文将结合开发笔记,从技术架构、实现细节到工程化思路,系统解析 HTML5 敲击乐应用的开发逻辑与实践技巧。

一、前端三剑客:交互式钢琴的技术基石

前端开发的核心在于 HTML、CSS、JavaScript 的协同工作,三者各司其职又紧密联动,构成了 HTML5 敲击乐应用的技术基石。HTML 作为 “结构骨架”,负责搭建钢琴键盘的物理形态;CSS 作为 “视觉外衣”,赋予键盘质感与美感;JavaScript 作为 “神经中枢”,实现敲击响应与音效播放,三者的有机结合让静态页面焕发生机。

HTML5 的文档声明<!DOCTYPE html>是项目启动的第一步,它明确了文档类型为 HTML5,确保浏览器以标准模式解析代码。与.txt、.pdf 等普通文档不同,HTMLDocument 作为结构化标记文档,通过标签对的形式组织内容 —— 在敲击乐项目中,开发者利用div标签构建键盘容器(块级元素,独占一行且支持宽高设置),用span标签承载琴键标识(行内元素,不独占一行且依赖内容撑开),快速完成 “键盘矩阵” 的结构搭建。值得一提的是,!+tab的快捷操作与 Emmet 语法大幅提升了结构编写效率,例如输入 “div.key*9” 即可一键生成 9 个琴键容器,让开发者专注于逻辑设计而非重复编码。

CSS 层叠样式表则解决了 “如何让键盘更逼真” 的问题。为消除不同浏览器的默认样式差异,项目引入 CSS Reset 技术 —— 摒弃性能低下的*通配符选择器,转而对bodydivspan等常用元素统一初始化,确保样式在多终端的一致性。在布局实现上,Flex 弹性布局成为核心方案:通过display: flex将琴键容器设置为弹性盒子,配合justify-content: centeralign-items: center实现琴键的水平、垂直居中,即便在不同尺寸的移动设备上,键盘也能自适应排列。背景处理方面,background-size: cover让背景图等比例缩放覆盖整个容器,background-position: bottom center确保背景定位精准,而no-repeat则避免图片重复,共同营造出沉浸式的演奏场景。单位选择上,项目摒弃固定的 px 单位,采用 rem(相对于根元素字体大小)与 vh(相对于视窗高度)等相对单位,通过设置 HTML 根元素font-size: 10px,实现 “一套代码适配多终端” 的响应式效果。

JavaScript 作为 “交互核心”,赋予了钢琴 “可演奏” 的能力。开发者通过监听键盘敲击或鼠标点击事件,触发琴键样式变化与音效播放 —— 当用户按下对应按键时,JS 动态为琴键添加 “按压” 类名,改变其背景色与位置,同时通过Audio对象播放预设的音符音效,实现 “视觉反馈 + 听觉输出” 的同步响应。将<script>标签置于<body>底部的做法,有效避免了 JS 加载对 HTML 解析的阻塞,确保页面先完成结构渲染,再初始化交互逻辑,提升了用户体验。

二、模块化开发:构建高可维护的前端应用

HTML5 敲击乐项目的开发过程,充分践行了 “模块化职责分离” 的工程思想,通过将结构、样式、交互拆分为独立模块,实现了代码的专业、可维护与可扩展。这种开发模式不仅符合大厂对前端工程化的要求,更是复杂应用开发的核心方法论。

在结构模块化层面,项目以 “功能模块” 为单位拆分 HTML 结构:顶部为标题区(h1标签),中间为核心键盘区(div.keyboard容器包裹 9 个div.key琴键),底部为控制区(音量调节、音色选择等)。每个模块通过唯一的类名标识,如.keyboard负责键盘整体布局,.key定义单个琴键样式,这种结构化设计让代码层级清晰,便于后续修改 —— 例如需要增加琴键数量时,只需在.keyboard中添加新的.key元素,无需改动整体结构。

样式模块化则通过 “分层设计” 实现:基础样式(如 CSS Reset、根元素字体设置)定义全局规则;布局样式(Flex 属性、容器宽高)控制模块位置;组件样式(琴键颜色、按压效果)描述具体元素外观;响应式样式(媒体查询、相对单位)适配不同设备。通过<link>标签在<head>中引入外部 CSS 文件,不仅实现了样式与结构的分离,更便于多人协作时的代码管理 —— 设计师可专注于 CSS 编写,开发者则聚焦 HTML 与 JS 逻辑,提升开发效率。

交互模块化是项目的核心亮点。JS 代码按 “功能职责” 拆分为事件监听模块、音效处理模块、样式控制模块:事件监听模块负责捕获键盘 / 鼠标事件,通过addEventListener绑定回调函数;音效处理模块预加载所有音符音频,提供playSound方法供事件模块调用;样式控制模块则封装activeKeydeactiveKey方法,处理琴键的按压与恢复状态。这种拆分让代码逻辑清晰,例如需要更换音效时,只需修改playSound方法中的音频路径,无需改动事件监听逻辑,大幅提升了代码的可扩展性。

三、工程化技巧:提升开发效率与应用性能

在 HTML5 敲击乐项目开发中,一系列工程化技巧的运用,不仅解决了多终端适配、浏览器兼容等问题,更显著提升了开发效率与应用性能,这些技巧也是大厂前端面试的高频考点。

开发效率方面,!+tab快捷生成 HTML 空结构、Emmet 语法快速编写 DOM 元素,让开发者摆脱了重复的标签输入工作;Live Server 工具的热更新功能,则实现了 “保存即刷新”,开发者无需手动刷新浏览器即可实时查看代码效果,大幅缩短了 “编写 - 调试” 周期。这些工具的运用,体现了前端开发 “高效迭代” 的核心诉求,也是现代前端工程化的基础配置。

多终端适配方面,相对单位的灵活运用成为关键。vh 单位让容器高度随视窗大小动态变化,确保在手机、平板、电脑等设备上,键盘始终占据合适比例;rem 单位则通过根元素字体大小的统一设置,实现了 “一处修改,全局响应”—— 例如在移动端将 HTMLfont-size调整为 8px,所有使用 rem 的元素会自动缩小,适配小屏幕。这种适配方案避免了传统 “多套 CSS” 的冗余,实现了 “自适应设计” 的最佳实践。

性能优化方面,CSS Reset 的合理使用消除了浏览器默认样式的冗余计算,提升了样式渲染效率;<script>标签置于底部的做法,避免了 JS 加载对 HTML 解析的阻塞,确保页面快速呈现;音频预加载则减少了用户首次敲击时的音效延迟,提升了交互流畅度。这些细节处理,体现了前端开发 “用户体验优先” 的核心思想,也是区分初级与高级前端开发者的重要标志。

四、技术价值与延伸:从模拟钢琴到前端生态

HTML5 敲击乐项目看似简单,却涵盖了前端开发的核心技术与工程思想,其价值不仅在于实现了一个交互式应用,更在于为前端开发者提供了 “从理论到实践” 的完整案例 —— 它展示了 HTML5 的结构化能力、CSS 的视觉美化能力、JavaScript 的交互实现能力,更诠释了模块化、工程化在前端开发中的重要性。

从技术延伸来看,该项目可进一步拓展为更复杂的音乐类应用:通过 Web Audio API 实现自定义音效合成,替代传统的音频文件播放;结合 Canvas 绘制动态音符可视化效果,提升用户体验;利用 LocalStorage 存储用户演奏记录,实现 “历史回放” 功能;通过 WebSocket 实现多人在线合奏,打造社交化音乐应用。这些延伸方向,既体现了 HTML5 技术的强大生态,也为前端开发者提供了更多的创新空间。

在大厂面试中,HTML5 文档声明的作用、块级元素与行内元素的区别、Flex 布局的属性用法、CSS Reset 的实现原理等,都是高频考点。而 HTML5 敲击乐项目恰好覆盖了这些知识点,通过实际项目理解这些概念,远比单纯背诵理论更有效 —— 它让开发者明白 “为什么这样设计”“如何解决实际问题”,从而真正掌握前端开发的核心能力。

结语

HTML5 敲击乐项目以 “模拟钢琴” 为载体,将前端三剑客技术、模块化开发思想、工程化技巧融为一体,为我们展现了前端开发 “创造体验” 的独特魅力。作为 JS 开发者,我们不仅是 “代码的编写者”,更是 “用户体验的导演”—— 通过结构化的 HTML 搭建骨架,用精美的 CSS 塑造外观,以灵活的 JavaScript 赋予灵魂,让静态的页面转化为可交互、可感知的数字产品。在前端技术飞速发展的今天,唯有扎实掌握基础技术、践行工程化思想、持续探索创新,才能在复杂的应用开发中游刃有余,打造出更多兼具美感与实用性的前端作品。

【搞发🌸活】不信书上那套理论!亲测Javascript能卡浏览器Reader一辈子~

点进来的前端佬,先别走!

让我详细给你逼逼叨!

93B1A00879A9B67271080936B8A2D89CE1D69417_size242_w423_h220.gif

在很久很久以前,前端圈就广泛流传,Javascript的加载和执行,都会阻塞浏览器Render。

然后过了这些日子,作为一名优秀的前端佬的意识爆发。

按照上面的说法,那是不是可以构造一个Javascript程序,让后续的CSS以及HTML文本永远都不能被解析Render到?

喔,觉的挺来劲的,说干就干!

image.png

前言

一开始构建了这么一个HTML,如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>demo</title>
</head>
<body>
  <h1 id="start" class="h1-title">开始渲染了</h1>
  <script>
    console.log(document.getElementById('start'))
  </script>
</body>
</html>
<script>
  // 此处插入代码
</script>
<h1 class="h1-title">看到这里就失败了!</h1>
<style>
  .h1-title {
    color: red;
  }
</style>

预想阻塞js代码会写在script标签里。

以上代码运行后如下:

image.png

以上展示的,因为没有填入代码,符合期望。

这里解释下为什么要将script脚本和h1要放在html之外。

因为根据各个资料上说,浏览器解读HTML文本就是从上往下解析的。当遇到</html>文档结束标签,就会开始生成DOM树+CSSOM树,并开始Render。

那我脑袋一拍,灵光一闪,自以为是的将需要Render的HTML和CSS放在</html>后,期望只Render第一行文字开始渲染了,而第二行文字看到这里就失败了!就永远得不到Render。

开始挑战!!!

方法一 递归

脑子第一个蹦出来的方法,就是用递归,来模拟JavaScript阻塞。

在上面HTML模板中填入如下代码:

function block() {
  Math.sqrt(Math.random());
  block();
}
block();

结果如下:

image.png

失败了,还在控制器里报了一个错误.RangeError: Maximum call stack size exceeded

oh,shit,明显这里我忽略了一个细节。

大家都知道的,Javascript是单线程运行机制。

而Javascript的函数分为解析和调用。解析有一个入栈的过程,调用有一个出栈过程。当入栈停止后,才会出栈被调用执行。而上面递归代码,构造了一个无限入栈的场景,结果就是直接撑爆内存。

很显然,浏览器识别到这种风险,直接作出报错处理。

失败~继续尝试!

方法二 while死循环

有了JS的单线程执行思路,顺理成章的,就有了使用while死循环,来模拟阻塞。

插入如下代码试一试。

while (true) {
  // 持续执行同步任务
  Math.sqrt(Math.random());
}

效果如下:

image.png

喔!成功了???? ★,°:.☆( ̄▽ ̄)/$:.°★

罗老师.gif

其实并没有~

之所以能有上面的效果,在于我使用了VSCODE中的Live Server插件,并构造了特殊的场景。基本原来就是Live Server是有热更新,我动态插入了</html>之后的代码到文件中。

image.png

究其原因,在现代浏览器中,浏览器有着强大的纠错机制。很多浏览器都不会遇到</html>就停止解析,忽略后续的文本。他们仍然会好心好意的将后续能看懂的文本,插入到<body>里去。

所以实际上,正常的去执行上面构造的代码,只能得到如下效果:

image.png

但现在离成功,也算走了一半!

动态插入的思路,让我想到了第三个方法。

方法三 按钮手动添加代码

这就是构造一个添加按钮,点击之后,动态添加上HTML标签和Script脚本。

初始是这个样子的:

image.png

HTML代码构造如下:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>demo</title>
</head>
<body>
  <h1 id="start" class="h1-title">开始渲染了</h1>
  <button id="execute">添加</button>
  <script>
    function sleepBlocking(ms) {
      const start = Date.now();
      while (Date.now() - start < ms) {
        // 什么都不做,纯粹阻塞
      }
    }
    document.getElementById('execute').onclick = () => {
      now1 = new Date().getTime()
      // 在前
      document.getElementById('execute').insertAdjacentHTML('beforebegin', `
          <h1 class='h1-title'>看到这里就失败了!</h1>
          <style>
            .h1-title {
              color: red;
            }
          <\/style>
        `)
      // 在后
      const script = document.createElement("script");
      // 5秒后执行
      script.innerHTML = 'sleepBlocking(5000);console.log("休眠后", new Date().getTime() - now1)'
      console.log('所有脚本添加后', new Date().getTime() - now1)
      document.body.appendChild(script);
    }
  </script>
</body>
</html>

从上面的代码可以看到,我弄了一个阻塞执行的5秒函数。接下来预期的效果就是:

点击前,先展示黑色的文字开始渲染了

点击添加按钮后,经过5秒后,就会使得所有文字变红,并出现看到这里就失败了!的效果,最终如下图:

image.png

符合预期!!完美~

以上就是整个验证的思路了,个人觉的基本可以回答标题上的问题。Javascript是真的会阻塞浏览器Render!!

另外还有一种思路,就是使用stream来构造一个一直会执行的远程脚本,为避免无聊,这里就不尝试了,都是大差不差的。

如果还能看到这里的前端佬,那我想说在这个尝试的过程还有一个意外,就是我们经常会看到很多技术类文档,解说Event Loop,都会用上宏任务和微任务解释,个人觉的有点牵强不太行。感兴趣接着往下看!

方法四 构造永不结束的“宏任务”?

先贴下Event Loop的一些解释:

  1. 从宏任务的头部取出一个任务执行;
  2. 执行过程中若遇到微任务则将其添加到微任务的队列中;
  3. 宏任务执行完毕后,微任务的队列中是否存在任务,若存在,则挨个儿出去执行,直到执行完毕;
  4. GUI 渲染;
  5. 回到步骤 1,直到宏任务执行完毕;

按照上面思路,是不是我我构造一个永远不结束的宏任务,也可以阻塞Render???

现在我把Javascript代码替换成如下:

function setTime() {
  Math.sqrt(Math.random());
  setTimeout(() => {
     setTime()
  }, 1)
}
setTime()

然后我们看到的效果确实是这样的。

image.png

并没有阻止,定时器任务还在依旧运行代码。

所以,我是不太相信网上那些所谓的事件循环的解释了!

另外我自己去找权威书籍《JavaScript高级程序设计(第4版)》 和 《JavaScript权威指南(第7版)》,英文版本,连那些词都没得~

嗯....先这样吧。

看到这里,我是想说,我这篇表情包很克制了!前端佬们给点小心心吧♥(ˆ◡ˆԅ)

新疆马蹄打了北鼻.gif

React中使用map+area标签实现img图片特定区域标记功能(可用Photoshop精准拾取对应点位)

需求描述

  • 前段时间,工作中笔者接到了一个有意思的需求
  • 就是给图片做特定的交互效果
  • 图片中有几个特殊区域
  • 当用户hover特殊区域的时候,高亮
  • 点击特殊区域的时候,弹框给到一些提示信息

效果图和线上演示

111.gif

线上效果演示地址:ashuai.site/reactExampl…

技术方案选择

  • 这个需求如果图片中的特定区域都是矩形
  • 我们完全可以直接使用通过定位的方式,写几个div去定位到相应的位置
  • 如下示例
<body>
  <!-- 父元素开启绝对定位 -->
  <div class="wrap">
    <!-- 图片和父元素宽高保持一致 -->
    <img src="./a.png" alt="a.png">
    <!-- 各个矩形设置宽高,再absolute微调定位到对应位置即可 -->
    <div class="rectangle1"></div>
    <div class="rectangle2"></div>
  </div>
</body>
  • 但若是图片中的特定区域不是常见的矩形
  • 而是一些特殊的样子形状
  • 直接使用div就不太好画出来了
  • 当然,我们也可以使用css的clip-path画出来特定稀奇古怪的形状
  • 但是,我们不太好控制形状的大小宽高半径之类的具体参数(不太好和图片中的区域保持一致)

一些clip-path的绘制效果

222.png
  • 上图的效果图代码,都在笔者整理的一些有意思的css效果仓库里
  • github.com/shuirongshu…
  • 仓库目前已经有57个有意思的css效果了
  • 后续笔者会不断新增,欢迎star

当然,也可以使用svg或者canvas去绘制稀奇古怪的几何图形,但是针对于这种需求,不是最佳实践

使用map和area标签映射图片关联

代码示意

  • 给img使用useMap取个唯一的名字(类似id),叫做#pathMap
  • 使用map映射刚刚的名字pathMap (#号类似锚点)
  • 这样的话,就完成了图片和图片地图的映射关系
  • 然后使用area标签,在图片的指定坐标打点,搭配shape形状,就能绘制出区域了
  <div>
    <img
        src={exampleImg}
        className="example-image"
        useMap="#pathMap"
    />
    <map name="pathMap">
        <area
            shape="rect"
            coords="84,188,200,255"
            href="#"
            onClick={(e) => { e.preventDefault(); showPathInfo('矩形区域'); }}
            title="矩形区域"
        />
    </map>
</div>

平面直角坐标系知识

  • 矩形两个点可以确定 84,188,200,255就是 x1 y1 x2 y2
  • 三角形三个点 439,158,513,284,366,284 就是 x1 y1 x2 y2 z1 z2
  • 圆形是一个点搭配一个半径 752,221,72就是 x1 y1 r (r半径的意思)
<area
    shape="rect"
    coords="84,188,200,255"
/>
<area
    shape="poly"
    coords="439,158,513,284,366,284"
/>
<area
    shape="circle"
    coords="752,221,72"
/>

使用Photoshop拾取对应点位

上述各个形状的各个坐标点,可以通过ps进行拾取确定

333.png444.png

使用jquery和jquery.maphilight进行区域审查效果

  • map、area这两个标签,F12审查dom元素看不到
  • 可以自己不停按下Tab键进行切换查看,但是依旧不直观
  • 这里我们可以借助jquery和jquery.maphilight这两个库
  • 在项目初始化的时候,进行一些对应的控制,如下
if ($ && $.fn.maphilight) {
    // 初始化maphilight,使用正确的选择器
    // jquery选中图片,使用jquery.maphilight.js提供的maphilight函数,进行高亮控制
    $('.example-image').maphilight({ 
        fill: true,          // 填充区域
        fillOpacity: 0.3,    // 填充透明度(0.3为半透明)
        // 为true的话,会初始高亮,为false的话,则是悬浮高亮
        // alwaysOn: true,
        alwaysOn: false,
    });
}

当然 我们要提前对应引入

<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/jquery.maphilight.js"></script>

完整代码

整个代码在笔者的github仓库里:github.com/shuirongshu…

import React, { useEffect } from 'react'
import exampleImg from '@/assets/img/example.png'
import { message } from 'antd'

export default function ImagePoint() {
    useEffect(() => {
        // 确保 jQuery 和 maphilight 已加载
        if ($ && $.fn.maphilight) {
            // 初始化maphilight,使用正确的选择器
            $('.example-image').maphilight({
                fill: true,          // 填充区域
                fillOpacity: 0.3,    // 填充透明度(0.3为半透明)
                // 为true的话,会初始高亮,为false的话,则是悬浮高亮
                // alwaysOn: true,
                alwaysOn: false,
            });
        }
    }, []);

    const showPathInfo = (pathName) => {
        message.info(pathName);
    };

    return (
        <div>
            <h2>图片区域打点</h2>
            <p>鼠标悬停在图片的矩形、多边形、圆形区域上查看高亮效果,点击查看信息</p>
            <img
                src={exampleImg}
                alt="example"
                className="example-image"
                useMap="#pathMap"
                style={{ width: 'fit-content', height: 'auto' }}
            />
            <map name="pathMap" id="pathMapID">
                <area
                    shape="rect"
                    coords="84,188,200,255"
                    href="#"
                    onClick={(e) => { e.preventDefault(); showPathInfo('矩形区域'); }}
                    alt="矩形"
                    title="矩形区域"
                />
                <area
                    shape="circle"
                    coords="752,221,72"
                    href="#"
                    onClick={(e) => { e.preventDefault(); showPathInfo('圆形区域'); }}
                    alt="圆形"
                    title="圆形区域"
                />
                <area
                    shape="poly"
                    coords="439,158,513,284,366,284"
                    href="#"
                    onClick={(e) => { e.preventDefault(); showPathInfo('多边形区域'); }}
                    alt="多边形"
                    title="多边形区域"
                />
            </map>
        </div>
    )
}

参考:

❌