1. 浏览器跨 Tab 窗口通信原理

浏览器跨 Tab 窗口通信原理

![01在这里插入图片描述

所谓多窗口下进行互相通信,是指在浏览器中,不同窗口(包括不同标签页、不同浏览器窗口甚至不同浏览器实例)之间进行数据传输和通信的能力。

当然,本文我们探讨的是纯前端的跨 Tab 页面通信,在非纯前端的方式下,我们可以借助诸如 Web Socket 等方式,藉由后端这个中间载体,进行跨页面通信。

在这里插入图片描述

本文我们更多的重心将放在,如何基于纯前端技术,实现多窗口下进行互相通信。

为了实现跨窗口通信,它应该需要具备以下能力:

  • 数据传输能力:能够将数据从一个窗口发送到另一个窗口,以及接收来自其他窗口的数据。
  • 实时性:能够实现实时或近实时的数据传输,以便及时更新不同窗口的内容。
  • 安全性:确保通信过程中的数据安全,防止恶意窃取或篡改通信数据。当然,这个不是本文讨论的重点,但是是实际应用中不应该忽视的一个重点。

方式一:Broadcast Channel ()

Broadcast Channel 是一个较新的 Web API,用于在不同的浏览器窗口、标签页或框架之间实现跨窗口通信。它基于发布 - 订阅模式,允许一个窗口发送消息,并由其他窗口接收。

其核心步骤如下:

  • 创建一个 BroadcastChannel 对象:在发送和接收消息之前,首先需要在每个窗口中创建一个 BroadcastChannel 对象,使用相同的频道名称进行初始化。
  • 发送消息:通过 BroadcastChannel 对象的 postMessage () 方法,可以向频道中的所有窗口发送消息。
  • 接收消息:通过监听 BroadcastChannel 对象的 message 事件,可以在窗口中接收到来自其他窗口发送的消息。

同时,Broadcast Channel 遵循浏览器的同源策略。这意味着只有在同一个协议、主机和端口下的窗口才能正常进行通信。如果窗口不满足同源策略,将无法互相发送和接收消息。

因为有同源限制,我们需要起一个服务,这里我基于 Vite 快速起了一个 Vue 项目,简单的基于 .vue 文件下进行一个演示。

其核心代码非常简单:

<template><div class="g-container" id="j-main">// ...</div>
</template><script setup>
import { onMounted } from 'vue'onMounted(() => {createBroadcastChannel()resizeEventBind()
})let broadcastChannel = nullconst createBroadcastChannel = () => {broadcastChannel = new BroadcastChannel('broadcast')broadcastChannel.onmessage = handleMessage
}const sendMessage = (data) => {broadcastChannel.postMessage(data)
}const handleMessage = (event) => {console.log('接收到 event', event)// TODO: 处理接收到信息后的逻辑
}const resizeEventBind = () => {window.addEventListener('resize', () => {const pos = getCurPos()sendMessage(pos)})
}// 计算当前元素距离显示器窗口右上角的距离
const getCurPos = () => {const barHeight = window.outerHeight - window.innerHeightconst element = document.getElementById('j-main')const rect = element.getBoundingClientRect()// 获取元素相对于屏幕左上角的 X 和 Y 坐标const x = rect.left + window.screenX // 元素左边缘相对于屏幕左边缘的距离const y = rect.top + window.screenY + barHeight // 元素顶部边缘相对于屏幕顶部边缘的距离return [x, y]
}
</script>

这里,我们的核心逻辑在于:

  • createBroadcastChannel() 函数用于创建一个 BroadcastChannel 对象,并设置消息处理函数。
  • sendMessage(data) 函数用于向 BroadcastChannel 发送消息。
  • handleMessage(event) 函数用于处理接收到的消息。
  • resizeEventBind() 函数用于监听窗口大小变化事件,并在事件发生时获取当前元素的位置信息,并通过 sendMessage() 函数发送位置信息到 BroadcastChannel。
  • getCurPos() 函数用于计算当前元素相对于显示器窗口右上角的距离。

onMounted() 生命周期钩子中,调用了 createBroadcastChannel()resizeEventBind() 函数,用于在组件挂载后执行相关的初始化操作。

这样,当我们同时打开两个窗口,移动其中一个窗口,就可以向另外一个窗口发生当前窗口希望传递过去的信息,在本例子中就是 #j-main 元素距离显示器右上角的距离。

假设 #j-main 只是一个在浏览器正中心矩形,我们同时打开两边的控制台,看看会发生什么:

在这里插入图片描述

可以看到,如果我们同时打开两个一个的页面,当触发右边页面的 Resize,左边的页面会收到基于 broadcastChannel.onmessage = handleMessage 接收到的信息,反之同理。

而一个完整的 Event 信息如下:
在这里插入图片描述

譬如,传递过来的信息放在 data 属性内、同时也可以获取当前的的 Broadcast Name 等。

基于 BroadcastChannel,就可以实现每个 Tab 内的核心信息互传, 可以得知当前在线设备数,再基于这些信息去完成我们想要的动画、交互等效果。

这里的核心点,还是:

  • 数据向其他 Tab 页面传递的能力
  • Tab 页面接受其他页面传递过来的数据的能力

其本质就是一个数据共享池子。

方式二:SharedWorker API

好,介绍完 Broadcast Channel(),我们再来看看 SharedWorker API。

SharedWorker API 是 HTML5 中提供的一种多线程解决方案,它可以在多个浏览器 TAB 页面之间共享一个后台线程,从而实现跨页面通信。

与其他 Worker 不同的是,SharedWorker 可以被多个浏览器 TAB 页面共享,且可以在同一域名下的不同页面之间建立连接。这意味着,多个页面可以通过 SharedWorker 实例之间的消息传递,实现跨 TAB 页面的通信。

它的实现与上面的 Broadcast Channel 非常类似,我们来看一看实际的代码:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="j-main">//...</div><script>let myWorkerfunction initWorker() {// 创建一个 SharedWorker 对象myWorker = new SharedWorker('./shared-worker.js', 'tabWorker')// 监听消息事件myWorker.port.onmessage = function (event) {console.log('接收到 event', event)}}function sendMessage(data) {// 发送消息myWorker.port.postMessage({ status: 'getData', data })}function resizeEventBind() {window.addEventListener('resize', () => {const pos = getCurPos()sendMessage(pos)})}function getCurPos() {const barHeight = window.outerHeight - window.innerHeightconst element = document.getElementById('j-main')const rect = element.getBoundingClientRect()// 获取元素相对于屏幕左上角的 X 和 Y 坐标const x = rect.left + window.screenX // 元素左边缘相对于屏幕左边缘的距离const y = rect.top + window.screenY + barHeight // 元素顶部边缘相对于屏幕顶部边缘的距离return [x, y]}window.onload = () => {initWorker()resizeEventBind()}window.onbeforeunload = () => {myWorker.port.postMessage({ status: 'close' })myWorker.port.close()}</script></body>
</html>

简单描述一下,上面也说了,跨 Tab 页通信的核心在于数据向外的发送与接收的能力:

  • initWorker() 方法中,使用 myWorker= new SharedWorker('/shared-worker.js', 'tabWorker') 创建了一个 SharedWorker , 后面每一个被打开的同域浏览器 TAB 页面,都是共享这个 Worker 线程,从而实现跨页面通信

  • 基于 myWorker.port.postMessage(data) 实现数据的传输

  • 基于 myWorker.port.onmessage = function() {} 实现传输数据的监听

当然,上面有引入一个 /shared-worker.js,这个是需要额外定义的,一个极简版本的代码如下:

// 储存所有port
const connections = []
onconnect = (e) => {const port = e.ports[0]!connections.includes(port) && connections.push(port)// 监听浏览器页签发送的消息port.onmessage = (e) => {switch (e.data.status) {case 'getData':// 广播:给所有port发消息broadcast(e.data.data)breakcase 'close':clearInvalidPort(port)breakdefault:break}}
}function broadcast(message) {connections.forEach((port) => {// 给浏览器页签发消息port.postMessage(message)})
}function clearInvalidPort(port) {const index = connections.findIndex((item) => item === port)if (~index) {connections.splice(index, 1)}
}

简单解析一下,下面对其进行解析:

  • 上面的代码中,定义了一个数组 connections,用于存储与 SharedWorker 建立连接的各个页面的端口对象;
  • onconnect 是事件处理程序,当有新的连接建立时会触发该事件;
  • 在 onconnect 函数中,通过 event.ports[0] 获取到与 SharedWorker 建立的连接的第一个端口对象,并将其添加到 connections 数组中,表示该页面与共享 Worker 建立了连接。
  • 在连接建立后,为每个端口对象设置了 onmessage 事件处理程序。当端口对象接收到消息时,会触发该事件处理程序。
  • 在 onmessage 事件处理程序中,通过遍历 connections 数组,将消息发送给除当前连接端口对象之外的所有连接。这样,消息就可以在不同的浏览器 TAB 页面之间传递。

总而言之,shared-worker.js 脚本创建了一个共享 Worker 实例,它可以接收来自不同页面的连接请求,并将接收到的消息发送给其他连接的页面。通过使用 SharedWorker API,实现跨 TAB 页面之间的通信和数据共享。

同理,我们来看看基于 Worker 的数据传输效果,同样是简化 DEMO,当 Resize 窗口时,向另外一个窗口发送当前窗口下 #j-main 元素的坐标:
在这里插入图片描述

可以看到,如果我们同时打开两个一个的页面,当触发右边页面的 Resize,左边的页面会利用 myWorker.port.onmessage = function() {} 收到基于 myWorker.port.postMessage(data) 发送的信息,反之同理。

而一个完整的 Event 信息如下:
在这里插入图片描述

可以看到,在 SharedWorker 方式中,传输数据与 Broadcast Channel 是一样的,都是利用 Message Event。简单对比一下:

  • SharedWorker 通过在多个 Tab 页面之间共享相同的 Worker 实例,方便地共享数据和状态,SharedWorker 需要多定义一个 shared-worker.js;
  • Broadcast Channel 通过向所有订阅同一频道的 Tab 页面广播消息,实现广播式的通信。

方式三:localStorage/sessionStorage

OK,最后一种跨 Tab 窗口通信的方式是利用 localStorage 、sessionStorage 本地化存储 API 以及的 storage 事件。

与上面 Broadcast Channel、SharedWorker 稍微不同的地方在于:

  • localStorage 方式,利用了本地浏览器存储,实现了同域下的数据共享;
  • localStorage 方式,基于 window.addEventListener('storage', function(event) {}) 事件实现了 localStore 变化时候的数据监听;

简单看看代码:

<template><div class="g-container" id="j-main">// ...</div>
</template><script>
import { ref, reactive, computed, onMounted } from 'vue'export default {setup() {function initLocalStorage() {let tabArray = JSON.parse(localStorage.getItem('tab_array'))if (!tabArray) {const tabIndex = 1id = tabIndexlocalStorage.setItem('tab_array', JSON.stringify([tabIndex]))} else {const tabIndex = tabArray[tabArray.length - 1] + 1id = tabIndexconst newTabArray = [...tabArray, tabIndex]localStorage.setItem('tab_array', JSON.stringify(newTabArray))}}function setLocalStorage(data) {localStorage.setItem(`tab_index_${id}`, JSON.stringify(data))}function handleMessage(data) {const rArray = JSON.parse(data)remoteX.value = rArray[0]remoteY.value = rArray[1]}function resizeEventBind() {window.addEventListener('resize', () => {const pos = getCurPos()setLocalStorage(pos)})window.addEventListener('storage', (event) => {console.log('localStorage 变化了!', event)console.log('键名:', event.key)console.log('变化前的值:', event.oldValue)console.log('变化后的值:', event.newValue)handleMessage(event.newValue)})}function getCurPos() {const barHeight = window.outerHeight - window.innerHeightconst element = document.getElementById('j-main')const rect = element.getBoundingClientRect()// 获取元素相对于屏幕左上角的 X 和 Y 坐标const x = rect.left + window.screenX // 元素左边缘相对于屏幕左边缘的距离const y = rect.top + window.screenY + barHeight // 元素顶部边缘相对于屏幕顶部边缘的距离return [x, y]}onMounted(() => {initLocalStorage()resizeEventBind()})return {}},
}
</script>

同样的简单解析一下:

  • 每次页面初始化时,都会首先有一个 initLocalStorage 过程,用于给当前页面一个唯一 ID 标识,并且存入 localStorage 中
  • 每次页面 resize,将当前页面元素 #j-main 的坐标值,通过 ID 标识当 Key,存入 localStorage 中
  • 其他页面,通过 window.addEventListener('storage', (event)=> {}) 监听 localStorage 的变化

交互传输结果,与上述两个动图是一致的,就不额外贴图了,但是基于 storage 事件传输的值有点不一样,我们展开看看:
在这里插入图片描述

我们通过 window.addEventListener('storage', (event)=> {}) ,可以拿到此次变化的 localStorage key 是什么,前值 oldValue 与 newValue 等等。

当然,由于 localStorage 存储过程只能是字符串,在读取的时候需要利用 JSON.stringify 和 JSON.parse 额外处理一层,调试的时候需要注意。

实际应用思考

当然,上面的实现其实有很大一个瑕疵。

那就是我们只顾着实现通信,没有考虑实际应用中的一些实际问题:

  • 如何确定何时开始通信?
  • Tab 页频繁的开关,如何知道当前还有多少页面处于打开状态?

基于实际应用,我们需要基于上述 3 种方式,进一步细化方案。

上面,为了方便演示,每次传输数据时,只传输动画需要的数据。而实际应用,我们可以需要细化整个传输数据,设定合理的协议。譬如:

 {// 传输状态:// 1 - 首次传输// 2 - 正常通信// 3 - 页面关闭status: 1 | 2 | 3,data: {}}

接收方需要基于收到信息所展示的不同的状态,做出不同的反馈。

当然,还有一个问题,我们如何知道页面被关闭了?基于组件的 onUnmounted 发送当前页面关闭的信息或者基于 window 对象的 beforeunload 事件发送当前页面关闭的信息?

这些信息都有可能因为 Tab 页面失活,导致关闭的信息无法正常被发送出去。所以,实际应用中,我们经常用的一项技术是心跳上报,一旦建立连接后,间隔 X 秒发送一次心跳广播,告诉其他接收端,我还在线。一旦超过某个时间阈值没有收到心跳上报,各个订阅方可以认为该设备已经下线。

总而言之,跨 Tab 窗口通信应用在实际应用的过程中,我们需要思考更多可能隐藏的问题。

跨 Tab 窗口通信应用场景

当然,除了最近大火的跨 Tab 动画应用场景,实际业务中,还有许多场景是它可以发挥作用的。这些场景利用了跨 Tab 通信技术,增强了用户体验并提供了更丰富的功能。

以下是一些常见的应用场景:

实时协作:多个用户可以在不同的 Tab 页上进行实时协作,比如编辑文档、共享白板、协同编辑等。通过跨 Tab 通信,可以实现实时更新和同步操作,提高协作效率。

譬如这个:
在这里插入图片描述

多标签页数据同步:当用户在一个标签页上进行了操作,希望其他标签页上的数据也能实时更新时,可以使用跨 Tab 通信来实现数据同步,保持用户在不同标签页上看到的数据一致性。

跨标签页通知:在某些场景下,需要向用户发送通知或提醒,即使用户不在当前标签页上也能及时收到。通过跨 Tab 通信,可以实现跨页面的消息传递,向用户发送通知或提醒。

多标签页状态同步:有些应用可能需要在不同标签页之间同步用户的状态信息,例如登录状态、购物车内容等。通过跨 Tab 通信,可以确保用户在不同标签页上看到的状态信息保持一致。

页面间数据传输:有时候用户需要从一个页面跳转到另一个页面,并携带一些数据,通过跨 Tab 通信可以在页面之间传递数据,实现数据的共享和传递。

举两个实际的例子:

  • 某系统是一个国际化电商的仓库管理系统,系统能切换到全球各地不同的仓库进行数据操作,当用户打开了页面后,又新开了一个 Tab 页面,并且切换到另外一个仓库进行操作。当用户重新回到第一个打开的页面时,为了防止用户错误操作数据(前端界面是一致的,可能忘记了自己切换过仓库),通过弹窗提醒用户你已经切换过仓库;
  • 某音乐播放器 PC 页面,在列表页面进行歌曲播放点击,如果当前没有音乐播放详情页,则打开一个新的播放详情页。但是,如果页面已经存在一个音乐播放详情页,则不会打开新的音乐播放详情页,而是直接使用已经存在的播放详情页面;

总之,跨 Tab 窗口通信在实时协作、数据同步、通知提醒等方面都能发挥重要作用,为用户提供更流畅、便捷的交互体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/262185.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web 前端 UI 框架Bootstrap简介与基本使用

Bootstrap 是一个流行的前端 UI 框架&#xff0c;用于快速开发响应式和移动设备优先的网页。它由 Twitter 的设计师和工程师开发&#xff0c;现在由一群志愿者维护。Bootstrap 提供了一套丰富的 HTML、CSS 和 JavaScript 组件&#xff0c;可以帮助开发者轻松地构建和定制网页和…

Springboot医院信息管理系统源码 带电子病历和LIS Saas应用+前后端分离+B/S架构

目录 系统特点 技术架构 系统功能 1、 标准数据维护 2、 收费&#xff08;门诊/住院&#xff09;系统 3、 药剂管理系统 4、 医生工作站系统 5、 护士工作站系统 6、电子病历系统 系统优点 云HIS系统简介 云HIS系统功能模块 门急诊挂号管理 门诊收费管理 门诊医…

Gitee教程2(完整流程)

1.配置git git config --global user.name "用户名" git config --global user.email "密码" 如何获取&#xff1f; gitee右上角加号点击新建仓库&#xff0c;仓库名随便起一个就行 找到这条命令&#xff0c;把这两句一个一个复制到vscode终端就行 2.创建g…

RabbitMQ的安装与使用

RabbitMQ的安装与使用 介绍一、RabbitMQ的安装1 查找镜像2 拉取镜像3 查看镜像4 创建容器5 查看容器6 访问测试 二、RabbitMQ的使用1 创建项目2 配置文件3 队列配置文件4 消费者5 生产者6 测试 三、交换器四、普通队列Demo五、死信队列Demo1 介绍2 示例2.1 配置2.2 生产者2.3 消…

【非常详细!】QT基础【二万字长文】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;QT从基础到进阶 1 QMake2 Qt中三个窗口部件的区别2.1 QMainWindow2.2 QWidget2.3 QDialog 3 Visual Studio的QT项目与QtCreater项目相互转换3.1 QtCreater项目转VS项目3.2 VS项目转QtCreat…

百度地图海量点方案趟坑记录(百度地图GL版 + MapVGL + vue3 + ts)

核心需求描述 不同层级有不同的海量图标展示底层海量图标需要展示文字拖动、放大缩小都需要重新请求数据并展示固定地图中心点&#xff08;拖动、放大缩小&#xff0c;中心点始终在地图中心&#xff09; 示例图片&#xff1a;&#xff08;某些图片涉及公司数据&#xff0c;就未…

苍穹外卖Day02——总结2

前期文章 文章标题地址苍穹外卖Day01——总结1https://blog.csdn.net/qq_43751200/article/details/135466359?spm1001.2014.3001.5501苍穹外卖Day01——解决总结1中存在的问题https://lushimeng.blog.csdn.net/article/details/135473412 总结2 前期文章1. 新增员工模块1.1 …

初阶数据结构之---顺序表和链表(C语言)

引言-线性表 线性表&#xff1a; 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构。线性表在逻辑上是线性结构&#xff0c;也就是说是连续的一条直线。但在物理上并不一定是连续的。线性表在物理上…

Django学习笔记-创建第一个django项目

1.创建一个虚拟环境的python项目 2.点击解释器设置 3.安装django包 4.终端选择Command Prompt 5.创建django项目运行django-admin startproject demo01(自命名) 6.修改连接数据库为mysql 7.修改语言(中国汉语)和时区(亚洲上海)USE_TZ改为False,否则时区不生效 8.修改TEMPLA…

并发List、Set、ConcurrentHashMap底层原理

并发List、Set、ConcurrentHashMap底层原理 ArrayList: List特点&#xff1a;元素有放入顺序&#xff0c;元素可重复 存储结构&#xff1a;底层采用数组来实现 public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Clon…

C 语言基本语法及实用案例分享

一、什么是 C 语言&#xff1f; C语言是一种较早的程序设计语言&#xff0c;诞生于1972年的贝尔实验室。1972 年&#xff0c;Dennis Ritchie 设计了C语言&#xff0c;它继承了B语言的许多思想&#xff0c;并加入了数据类型的概念及其他特性。C语言是一门面向过程的计算机编程语…

Unity NavMesh 清除不可行走区域

通常场景中物体设置为static或Navigation Static后&#xff0c;打开Navigation使用默认设置烘焙NavMesh&#xff0c;模型顶部和底部会出现蓝色网格&#xff0c;但其中有部分属于不可能到达区域&#xff0c;如下图 本文介绍两种可去掉NavMesh中不需要网格的方法&#xff1a; 方…

OpenCV运行gstreamer管道获取相机数据,处理以后,再交给gstreamer显示(QT实现)

效果: 前言 无意中发现,OpenCV也可以运行gstreamer的命令管道,然后使用appsink来与OpenCV连接起来进行处理,在不断测试之下,先后实现了以下功能: 1. OpenCV运行gstreamer命令,通过appsink传递给OpenCV显示 2. OpenCV运行gstreamer命令,然后再把Mat图像数据通过appsrc传…

(3)(3.6) 用于OpenTX的Yaapu遥测脚本

文章目录 前言 1 安装和操作 2 参数说明 前言 这是一个开源 LUA 脚本&#xff0c;用于在使用 OpenTX 2.2.3 的 Horus X10、X12、Jumper T16、T18、Radiomaster TX16S、Taranis X9D、X9E、QX7 和 Jumper T12 无线电设备上显示 FrSky 的直通遥测数据(FrSky passthrough telem…

解决IntelliJ IDEA 2023版本创建Spring项目时Java只能选择17或21的问题

问题描述&#xff1a; 当使用IntelliJ IDEA2023版本中Spring Initializr新建Spring项目时&#xff0c;即使JDK配置项为1.8&#xff0c;Java配置项仍然只能选17或21. 在JDK为1.8版本情况下&#xff0c;Java选择17或21&#xff0c;点击NEXT按钮&#xff0c;则会弹窗提示SDK不支持…

航空航天5G智能工厂数字孪生可视化平台,推进航空航天数字化转型

航空航天5G智能工厂数字孪生可视化平台&#xff0c;推进航空航天数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业关注的焦点。航空航天业作为高端制造业的代表&#xff0c;也在积极探索数字化转型之路。为了更好地推进航空航天数字化转型&#xff0c;一…

安卓游戏开发之音频技术优劣分析

一、引言 在安卓游戏开发中&#xff0c;音频处理技术扮演着至关重要的角色&#xff0c;它不仅能够增强游戏的沉浸感和玩家体验&#xff0c;还能通过声音效果传达关键的游戏信息。以下将对几种常见的安卓游戏音频处理技术进行优劣分析&#xff0c;并结合应用场景来阐述其特点。 …

十九、图像的放缩和插值

项目功能实现&#xff1a;对一张图像进行放大和缩小操作 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 resizing.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class RESIZING { public:void resizing(Mat& image); };#pragma…

【Linux】进程状态

进程状态 进程状态的简要介绍运行状态进程排队 阻塞状态挂起状态Linux中的进程状态 进程状态的简要介绍 进程状态指的是一个操作系统中正在运行的进程当前所处的状态。根据不同的操作系统&#xff0c;进程状态可能会有一些细微的差别&#xff0c;但最主要的是以下三种状态 运行…

电路设计(26)——速度表的multisim仿真

1.设计要求 设计一款电路&#xff0c;能够实时显示当前速度。 用输入信号模拟行驶的汽车&#xff0c;信号频率的1hz代表汽车速度的1m/s。最后速度显示&#xff0c;以km/h为单位。 2.电路设计 当输入信号频率为40HZ时&#xff0c;显示的速度应该为144KM/h&#xff0c;仿真结果为…