在Electron中实现实时下载进度显示的完整指南
在开发Electron应用时,提供良好的用户体验至关重要,尤其是在下载大文件时。用户需要知道下载进度、预计完成时间以及当前下载速度。本文将详细介绍如何在Electron应用中实现实时下载进度显示功能,从主进程到渲染进程的完整流程。
技术栈是electron+vue3作为示例,其它的技术栈同样可以使用
系统架构概述
实现下载进度显示功能需要以下三个主要组件协同工作:
- 主进程(Main Process):负责实际的文件下载和进度跟踪
- 预加载脚本(Preload Script):安全地暴露主进程的功能和事件给渲染进程
- 渲染进程(Renderer Process):负责显示下载进度界面和用户交互
下面是整个系统的工作流程:
主进程(main.js) ─┐│ IPC通信
预加载脚本(preload.js) ─┐│ 暴露API
渲染进程(App.vue + DownloadModal.vue)
实现步骤
1. 主进程中实现下载和进度跟踪
首先,在main.js中实现下载处理器,并添加进度跟踪逻辑:
// client/electron/main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');// 处理下载请求
ipcMain.handle('download-update', async (event, options) => {try {const { url, filename, version } = options;if (!url || !filename) {return { success: false, error: '下载地址或文件名无效' };}// 准备下载路径,要下载到哪个目录下,userHomeDir系统的默认主目录const userHomeDir = os.homedir();const downloadDir = path.join(userHomeDir, '要下载到的目录名');// 确保目录存在if (!fs.existsSync(downloadDir)) {fs.mkdirSync(downloadDir, { recursive: true });}// 确定下载文件路径let filePath;if (process.platform === "win32") {filePath = path.join(downloadDir, '文件名.exe');} else {filePath = path.join(downloadDir, '文件名');}// 开始下载文件const result = await downloadFileWithProgress(event, url, filePath);return result;} catch (error) {console.error('下载失败:', error);return { success: false, error: error.message };}
});// 实现带进度的下载函数
async function downloadFileWithProgress(event, url, filePath) {return new Promise((resolve, reject) => {// 根据URL选择协议const requester = url.startsWith('https') ? https : http;console.log('文件将下载到:', filePath);const request = requester.get(url, (response) => {// 处理重定向if (response.statusCode === 301 || response.statusCode === 302) {const redirectUrl = response.headers.location;console.log('下载重定向到:', redirectUrl);return resolve(downloadFileWithProgress(event, redirectUrl, filePath));}// 获取文件大小const totalSize = parseInt(response.headers['content-length'], 10);let downloadedSize = 0;let lastProgressTime = Date.now();let lastDownloadedSize = 0;// 创建文件写入流const file = fs.createWriteStream(filePath);// 监听数据接收事件,更新进度response.on('data', (chunk) => {downloadedSize += chunk.length;const percent = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0;// 计算下载速度 (每秒更新一次)const now = Date.now();const elapsedTime = now - lastProgressTime;if (elapsedTime >= 1000 || percent === 100) {const bytesPerSecond = Math.round((downloadedSize - lastDownloadedSize) / (elapsedTime / 1000));// 将下载大小格式化为可读的字符串const formattedDownloaded = formatBytes(downloadedSize);const formattedTotal = formatBytes(totalSize);const formattedSpeed = formatBytes(bytesPerSecond) + '/s';// 更新最后进度时间和大小lastProgressTime = now;lastDownloadedSize = downloadedSize;// 发送进度给渲染进程event.sender.send('download-progress', {percent: percent,downloaded: downloadedSize,total: totalSize,formattedDownloaded: formattedDownloaded,formattedTotal: formattedTotal,speed: formattedSpeed});}});// 将响应导入文件response.pipe(file);// 监听文件写入完成事件file.on('finish', () => {file.close();console.log('文件下载完成:', filePath);resolve({ success: true, filePath });});// 监听错误file.on('error', (err) => {fs.unlink(filePath, () => {});console.error('文件写入错误:', err);reject(err);});});// 处理请求错误request.on('error', (err) => {console.error('下载失败:', err);fs.unlink(filePath, () => {}); // 删除可能部分下载的文件reject(err);});});
}// 辅助函数:格式化字节大小
function formatBytes(bytes, decimals = 2) {if (bytes === 0) return '0 Bytes';const k = 1024;const dm = decimals < 0 ? 0 : decimals;const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];const i = Math.floor(Math.log(bytes) / Math.log(k));return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
主进程实现了以下关键功能:
- 通过
downloadFileWithProgress函数下载文件,同时跟踪进度 - 计算下载百分比、下载速度和格式化的文件大小
- 使用
event.sender.send()方法向渲染进程发送实时进度更新 - 处理重定向、错误和完成事件
- 包含辅助函数
formatBytes将字节大小转换为可读格式
2. 预加载脚本中暴露下载功能和事件
在preload.js中,我们需要安全地暴露下载功能和进度事件给渲染进程:
// client/electron/preload.js
const { contextBridge, ipcRenderer } = require('electron');// 安全地暴露主进程功能给渲染进程
contextBridge.exposeInMainWorld('electron', {// 下载文件APIdownloadUpdate: (options) => {return ipcRenderer.invoke('download-update', options);},// 下载进度事件监听器onDownloadProgress: (callback) => {// 移除可能存在的旧监听器ipcRenderer.removeAllListeners('download-progress');// 添加新的监听器ipcRenderer.on('download-progress', (event, progressData) => {callback(progressData);});// 返回清理函数return () => {ipcRenderer.removeAllListeners('download-progress');};}
});
预加载脚本完成了两个关键任务:
- 暴露
downloadUpdate方法,使渲染进程能够调用主进程的下载功能 - 暴露
onDownloadProgress事件监听器,使渲染进程能够接收下载进度更新 - 提供清理函数,确保不会留下多余的事件监听器
3. 渲染进程中接收和处理下载进度
在App.vue中,我们需要设置状态变量和事件监听器来处理下载进度:
// client/src/App.vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import DownloadModal from '@/components/DownloadModal.vue';// 下载状态
const downloadState = ref({visible: false,fileName: '',url: '',version: '',percentage: 0,downloadedSize: '0 KB',totalSize: '0 MB',speed: '0 KB/s'
});// 下载对话框引用
const downloadModalRef = ref(null);// 清理函数引用
let cleanupProgressListener = null;// 组件挂载时设置进度监听器
onMounted(() => {if (window.electron && window.electron.onDownloadProgress) {cleanupProgressListener = window.electron.onDownloadProgress((progressData) => {// 更新下载状态downloadState.value.percentage = progressData.percent;downloadState.value.downloadedSize = progressData.formattedDownloaded;downloadState.value.totalSize = progressData.formattedTotal;downloadState.value.speed = progressData.speed;console.log(`下载进度: ${progressData.percent}%, 速度: ${progressData.speed}`);});}
});// 组件卸载时清理监听器
onUnmounted(() => {if (cleanupProgressListener) {cleanupProgressListener();}
});// 确认更新开始下载
const confirmUpdate = async () => {// 隐藏确认对话框updateConfirm.value.visible = false;// 重置下载状态downloadState.value = {visible: true,fileName: process.platform === 'win32' ? 'secmate.exe' : 'secmate',url: updateConfirm.value.url,version: updateConfirm.value.version,percentage: 0,downloadedSize: '0 KB',totalSize: '计算中...',speed: '0 KB/s'};// 显示下载对话框if (downloadModalRef.value) {downloadModalRef.value.startDownload();}try {if (!window.electron || !window.electron.downloadUpdate) {throw new Error('下载功能不可用');}// 开始下载const result = await window.electron.downloadUpdate({url: updateConfirm.value.url,filename: downloadState.value.fileName,version: updateConfirm.value.version});console.log('下载结果:', result);if (result.success) {// 下载成功,完成下载动画if (downloadModalRef.value) {downloadModalRef.value.completeDownload();}} else {// 下载失败showMessage('下载失败: ' + (result.error || '未知错误'), 'error');downloadState.value.visible = false;}} catch (error) {console.error('下载过程出错:', error);showMessage('下载过程出错: ' + error.message, 'error');downloadState.value.visible = false;}
};// 处理下载完成
const handleDownloadComplete = async () => {console.log('下载已完成');// 添加版本信息到成功消息const versionText = downloadState.value.version ? ` (版本 ${downloadState.value.version})` : '';showMessage(`更新文件已下载完成${versionText}!`, 'success');// 关闭下载状态downloadState.value.visible = false;// 启动后端服务或其他后续操作...
};
</script><template><!-- 下载进度弹窗 --><DownloadModal:visible="downloadState.visible":fileName="downloadState.fileName":progress="downloadState.percentage":total="downloadState.totalSize":downloadState="downloadState"ref="downloadModalRef"@complete="handleDownloadComplete"/><!-- 其他组件... -->
</template>
App.vue中的关键实现包括:
- 创建
downloadState响应式对象,存储下载状态信息 - 使用
onMounted和onUnmounted生命周期钩子管理进度事件监听器 - 在
confirmUpdate函数中开始下载流程并重置下载状态 - 将下载状态传递给
DownloadModal组件显示进度信息 - 通过
handleDownloadComplete处理下载完成后的逻辑
4. 创建下载进度显示组件
最后,创建DownloadModal.vue组件来显示下载进度:
<!-- client/src/components/DownloadModal.vue -->
<template><div class="download-modal" v-show="visible"><div class="download-dialog"><div class="download-header"><img width="24px" height="24px" src="@/assets/zhuce.png" alt="下载" class="download-emoji"><h3>正在下载…</h3></div><div class="progress-container"><!-- 进度条 --><div class="progress-bar"><div class="progress-fill" :style="{ width: `${progress}%` }"></div></div><!-- 进度信息 --><div class="progress-stats"><div class="progress-text">{{ progress }}%</div><div class="progress-size">{{ downloadState.downloadedSize }}/{{ downloadState.totalSize }}</div></div><!-- 下载速度 --><div class="download-speed" v-if="downloadState.speed">{{ downloadState.speed }}</div></div><div class="download-message">{{ message }}</div></div></div>
</template><script setup>
import { ref, watch } from 'vue';const props = defineProps({visible: Boolean,progress: {type: Number,default: 0},fileName: String,downloadState: {type: Object,default: () => ({downloadedSize: '0 KB',totalSize: '0 MB',speed: '0 KB/s'})}
});const emit = defineEmits(['complete']);// 状态变量
const message = ref('');
let progressInterval = null;// 监听进度变化,更新提示消息
watch(() => props.progress, (newProgress) => {if (newProgress < 30) {message.value = '正在下载更新包...';} else if (newProgress < 60) {message.value = '正在下载更新包...';} else if (newProgress < 90) {message.value = '下载中,请稍候...';} else {message.value = '即将完成下载...';}
});// 开始下载动画
function startDownload() {// 清除可能存在的旧计时器if (progressInterval) clearInterval(progressInterval);// 设置初始消息message.value = '正在连接下载服务器...';
}// 完成下载
function completeDownload() {clearInterval(progressInterval);message.value = '下载完成!';// 延迟关闭setTimeout(() => {emit('complete');}, 1000);
}// 暴露方法给父组件
defineExpose({startDownload,completeDownload
});
</script><style scoped>
.download-modal {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;z-index: 10000000;
}.download-dialog {width: 380px;background-color: white;border-radius: 14px;padding: 30px;box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}.download-header {display: flex;align-items: center;margin-bottom: 20px;
}h3 {margin: 0;font-size: 18px;color: #333;
}.progress-container {margin-bottom: 16px;
}.progress-bar {height: 8px;background-color: #f0f0f0;border-radius: 4px;overflow: hidden;
}.progress-fill {height: 100%;background: linear-gradient(90deg, #7af2ff, #477cff);width: 0;border-radius: 4px;transition: width 0.3s ease;
}.progress-stats {display: flex;justify-content: space-between;margin-top: 8px;
}.progress-text, .progress-size {font-size: 14px;color: #666;
}.download-speed {text-align: right;margin-top: 4px;font-size: 13px;color: #888;
}.download-message {text-align: center;font-size: 14px;color: #555;min-height: 20px;margin-top: 10px;
}
</style>
DownloadModal组件的核心功能包括:
- 接收并显示下载进度、文件大小和下载速度
- 提供动态进度条,显示当前下载百分比
- 根据进度显示相应的提示消息
- 提供
startDownload和completeDownload方法供父组件调用
关键技术点解析
1. 实时进度计算和格式化
在主进程中,我们不仅计算下载百分比,还计算下载速度并格式化文件大小:
// 计算下载速度
const now = Date.now();
const elapsedTime = now - lastProgressTime;if (elapsedTime >= 1000 || percent === 100) {const bytesPerSecond = Math.round((downloadedSize - lastDownloadedSize) / (elapsedTime / 1000));// 格式化大小为可读字符串const formattedDownloaded = formatBytes(downloadedSize);const formattedTotal = formatBytes(totalSize);const formattedSpeed = formatBytes(bytesPerSecond) + '/s';// 更新最后进度时间和大小lastProgressTime = now;lastDownloadedSize = downloadedSize;// 发送进度数据...
}
2. 安全的IPC通信
通过预加载脚本,我们安全地桥接了主进程和渲染进程的通信:
// 在预加载脚本中暴露事件监听器
onDownloadProgress: (callback) => {ipcRenderer.removeAllListeners('download-progress');ipcRenderer.on('download-progress', (event, progressData) => {callback(progressData);});return () => {ipcRenderer.removeAllListeners('download-progress');};
}
3. 响应式UI更新
在Vue组件中,我们使用响应式对象和计算属性来确保UI与下载状态同步:
// 通过 props 将下载状态传递给组件
<DownloadModal:visible="downloadState.visible":progress="downloadState.percentage":downloadState="downloadState"ref="downloadModalRef"@complete="handleDownloadComplete"
/>// 在组件内部监听进度变化
watch(() => props.progress, (newProgress) => {if (newProgress < 30) {message.value = '正在下载更新包...';} else if (newProgress < 60) {message.value = '正在下载更新包...';} else if (newProgress < 90) {message.value = '下载中,请稍候...';} else {message.value = '即将完成下载...';}
});
最佳实践与优化建议
-
节流进度更新:对于较大的文件,每个数据块都发送进度更新会导致性能问题。我们使用时间间隔(每秒更新一次)来节流进度更新。
-
格式化显示大小:使用
formatBytes函数将字节数转换为人类可读的格式,如KB、MB和GB。 -
提供下载速度:显示当前下载速度,帮助用户估计剩余时间。
-
正确清理资源:在组件卸载时清理事件监听器,避免内存泄漏。
-
显示不同阶段的消息:根据下载进度显示不同的提示消息,增强用户体验。
-
处理下载错误:捕获并显示下载过程中的错误,提供有意义的错误信息。
-
保留下载历史:可以考虑添加下载历史记录功能,允许用户查看和管理历史下载。
应用场景
这种实时下载进度显示功能可以应用于多种场景:
- 应用自动更新:显示新版本下载进度
- 大文件下载:下载大型资源文件,如视频、音乐或文档
- 插件安装:下载和安装第三方插件或扩展
- 批量下载:同时下载多个文件并显示总体进度
- 数据导入/导出:在数据迁移过程中显示进度
总结
在Electron应用中实现实时下载进度显示是提升用户体验的重要一环。通过主进程跟踪下载进度、预加载脚本安全地暴露IPC通信,以及渲染进程中的响应式UI更新,我们可以创建一个流畅、信息丰富的下载体验。
这种架构不仅保证了安全性,还提供了良好的性能和用户体验。通过显示下载百分比、文件大小和下载速度,用户可以清楚地了解下载状态,减少等待过程中的焦虑感。
相关文章:
在Electron中实现实时下载进度显示的完整指南
在开发Electron应用时,提供良好的用户体验至关重要,尤其是在下载大文件时。用户需要知道下载进度、预计完成时间以及当前下载速度。本文将详细介绍如何在Electron应用中实现实时下载进度显示功能,从主进程到渲染进程的完整流程。 技术栈是ele…...
java生成一个可以下载的word文件
在 Java 里,你能够借助 Apache POI 库来生成 Word 文件,并且实现文件下载功能。下面为你详细介绍实现步骤和示例代码。 1. 添加依赖 若使用 Maven 项目,需在 pom.xml 里添加 Apache POI 的依赖: <dependencies><depen…...
MacBook部署达梦V8手记
背景 使用Java SpringBootDM开发Web应用,框架有License,OSX加载dll失败,安装了Windows 11,只有一个C盘,达梦安装后因为C盘权限问题,创建数据库失败,遂采用Docker容器方式部署。 下载介质 官网在…...
外贸 B2B 平台没落?多语言批发系统正在崛起
近年来,全球外贸行业正在发生快速变化,传统的 B2B 平台正面临越来越多的挑战,尤其是在面对新兴的多语言批发系统时。这种变化不仅影响了供应商和买家之间的交易方式,也正在推动外贸行业的数字化升级和转型。今天,让我们…...
[spring] Spring JPA - Hibernate 多表联查 1
[spring] Spring JPA - Hibernate 多表联查 之前在 [spring] spring jpa - hibernate 名词解释&配置 和 [spring] spring jpa - hibernate CRUD 简单的学习了一下怎么使用 Hibernate 实现 CRUD 操作,不过涉及到的部分都是逻辑上比较简单的实现——只在一张表上…...
鸿蒙Next开发实战教程—电影app
最近忙忙活活写了不少教程,但是总感觉千篇一律,没什么意思,大家如果有感兴趣的项目可以私信给幽蓝君写一写。 今天分享一个电影App。 这个项目也比较简单,主要是一些简单页面的开发和本地视频的播放以及横竖屏切换。 页面搭建以…...
共享栈 线程局部存储 线程互斥 线程同步 消费者生产者模型
共享栈 第一个主线程会在栈区 而当其他线程创建时实在共享区动态申请的栈区 线程局部存储 __thread 关键字 与编译有关 全局变量是被线程共享的 每个线程都能看到 修改 但是如果对该全局变量加上__thread关键字后 该全局变量就不会被共享 将变量在库中的每一个线程的属…...
停车场停车位数据集,标注停车位上是否有车,平均正确识别率99.5%,支持yolov5-11, coco json,darknet,xml格式标注
停车场停车位数据集,标注停车位上是否有车,平均正确识别率98.0%,支持yolov5-11, coco json,darknet,xml格式标注 数据集-识别停车场所有车辆的数据集 数据集分割 一共184张图片 训练组 89&am…...
【Go】运算符笔记
基本数学运算 Go 语言支持常见的 算术运算符,用于执行数学计算。 运算符说明加法-减法*乘法/除法%取余自增--自减 整数运算只能得到整数部分 package mainimport ("fmt""math" )func main() {go_math() }func go_math() {x, y : 8, 5fmt.Pr…...
ssm框架之mybatis框架讲解
1,Mybatis 1.1 Mybatis概述 1.1.1 Mybatis概念 MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2…...
CEF 多进程模式时,注入函数,获得交互信息
CEF 控制台添加一函数,枚举 注册的供前端使用的CPP交互函数有哪些-CSDN博客 上篇文章,是在模拟环境,单进程中设置的,这篇文章,将其改到正常多进程环境中设置。 对应于工程中的 CEF_RENDER项目 一、多进程模式中,改写 修改步骤 1、注入函数 client_app_render.cpp 在…...
Androidstudio出现警告warning:意外的元素
这些警告信息通常与 Android SDK 或系统镜像的配置文件有关,可能是由于 SDK 工具或系统镜像的版本不兼容或配置文件格式发生了变化。以下是解决这些警告的步骤: 1. 更新 Android SDK 工具 确保你使用的是最新版本的 Android SDK 工具: 打开…...
深入了解Linux —— git三板斧
版本控制器git 为了我们方便管理不同版本的文件,就有了版本控制器; 所谓的版本控制器,就是能够了解到一个文件的历史记录(修改记录);简单来说就是记录每一次的改动和版本迭代的一个管理系统,同…...
Vala编程语言教程-运算符
运算符 赋值操作。左操作数必须为标识符,右操作数必须为适当的值或引用。 , -, /, *, % 基础算术运算,作用于左右操作数。 运算符也可用于字符串拼接。 , -, /, *, % 左右操作数间算术运算,左操作数必须为标识符,运…...
C#本地将labelme数据集转换为机器视觉yolo数据集格式
C#本地,将labelme数据集转换为机器视觉yolo数据集格式 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Text.RegularExpressions; using System.Text.U…...
【软件系统架构】单体架构
一、引言 在软件开发的漫长历程中,架构的选择一直是至关重要的决策。单体架构作为一种经典的架构模式,曾经在许多项目中发挥着不可替代的作用。虽然如今微服务等架构逐渐流行,但理解单体架构对于深入掌握软件架构体系仍然有着重要意义。 二、…...
【求助】【建议放弃】【谷粒商城版】Kubernetes
本文作者: slience_me 文章目录 Kubernetes【谷粒商城版】【建议放弃】1. docker安装2. kubernetes安装前3. kubeadm,kubelet,kubectl3.1 简介kubeadmkubeletkubectl常用指令 3.2 安装3.3 kubeadm初始化3.4 加入从节点(工作节点)3.5 安装Pod网络插件(CNI…...
uniapp 实现微信小程序电影选座功能
拖动代码 /*** 获取点击或触摸事件对应的座位位置* 通过事件对象获取座位的行列信息* param {Event|TouchEvent} event - 点击或触摸事件对象* returns {Object} 返回座位位置对象,包含行(row)和列(col)信息,若未找到有效位置则返回 {row: -1, col: -1}*…...
python+flask实现360全景图和stl等多种格式模型浏览
1. 安装依赖 pip install flask 2. 创建Flask应用 创建一个基本的Flask应用,并设置路由来处理不同的文件类型。 from flask import Flask, render_template, send_from_directory app Flask(__name__) # 设置静态文件路径 app.static_folder static app.r…...
IntelliJ 配置文件plugin.xml
在 IntelliJ IDEA 插件开发中,plugin.xml 是插件的配置文件,它包含了关于插件的所有基本信息、扩展点、依赖关系等。该文件使用 XML 格式进行定义。以下是 plugin.xml 中常见的元素及其用途: <idea-plugin><!-- 插件的基本信息 --&…...
C# Unity 唐老狮 No.10 模拟面试题
本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: Unity课程 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho C# 1. 内存中,堆和…...
数据库系统——规范化1NF~BCNF
数据库规范化完全指南:从零到BCNF,中学生也能秒懂!📚✨ 一、什么是数据库规范化? 科学定义 🔍 数据库规范化是通过一系列规则(范式)将数据库表结构分解为更小、更高效、无冗余的表…...
第十五届蓝桥杯2024JavaB组省赛试题A:报数游戏
简单的找规律题目。题目给得数列,第奇数项是20的倍数,第偶数项时24的倍数。题目要求第n 202420242024 项是多少。这一项是偶数,所以答案一定是24的倍数,并且偶数项的个数和奇数项的个数各占一半,所以最终的答案ans( n…...
Matlab 汽车二自由度转弯模型
1、内容简介 Matlab 187-汽车二自由度转弯模型 可以交流、咨询、答疑 2、内容说明 略 摘 要 本文前一部分提出了侧偏角和横摆角速度作为参数。描述了车辆运动的运动状态,其中文中使用的参考模型是二自由度汽车模型。汽车速度被认为是建立基于H.B.Pacejka的轮胎模…...
关于 2>/dev/null 的作用以及机理
每个进程都有三个标准文件描述符:stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。默认情况下,stderr会输出到终端。使用2>可以将stderr重定向到其他地方,比如…...
学c++的人可以几天速通python?
学了俩天啊,文章写纸上了 还是蛮有趣的...
HTML,CSS,JavaScript
HTML:负责网页的结构(页面元素和内容)。 CSS:负责网页的表现(页面元素的外观、位置等页面样式,如:颜色、大小等)。 Javascript:负责网页的行为(交互效果)。 MDN前端开发文档(MDN Web Docs) HTML HTML(HyperText Markup Language):超文本标记语言超文本:超越了文本的…...
微信小程序面试内容整理-图片优化
在微信小程序中,图片优化是提升加载速度、节省网络带宽和提高用户体验的重要步骤。图片通常是小程序页面中的主要资源,合理的图片优化能显著提高小程序的性能,尤其是在用户网络状况较差的情况下。 1. 选择合适的图片格式 不同的图片格式有不同的特点,选择合适的格式能够有效…...
Rocky Linux 9.x 基于 kubeadm部署k8s 1.32
一、部署说明 1、主机操作系统说明 序号操作系统及版本备注1Rocky Linux release 9下载链接:https://mirrors.163.com/rocky/9.5/isos/x86_64/Rocky-9.5-x86_64-minimal.iso 2、主机硬件配置说明 作用IP地址操作系统配置关键组件k8s-master01192.168.234.51Rocky…...
【每日学点HarmonyOS Next知识】上下拉列表、停止无限循环动画、页面列表跟随列表滑动、otf字体、日期选择
1、HarmonyOS 实现只需要保留上拉加载更多,但是不需要下拉刷新? Refresh通过参数refreshing判断当前组件是否正在刷新,可以控制该参数变化来触发下拉刷新:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5…...
