jupyter-themes自定义加载进度条:可视化主题加载进度
痛点解析:为什么需要主题加载进度条?
在使用Jupyter Notebook进行数据分析或开发时,我们经常会遇到主题切换缓慢的问题。特别是当我们选择了一个新的主题或者自定义了字体、颜色等参数后,Jupyter Notebook需要重新加载主题资源,这个过程通常需要几秒钟到十几秒钟不等。然而,默认情况下,Jupyter Notebook并没有提供任何可视化的加载进度提示,这让用户无法判断主题加载的状态,不知道是加载中还是出现了错误。这种不确定性不仅影响了用户体验,还可能导致用户重复操作,进一步延长了加载时间。
为了解决这个问题,我们可以通过自定义JavaScript脚本来实现一个主题加载进度条,实时显示主题加载的进度,让用户能够清晰地了解加载状态,提升使用体验。
实现原理:深入了解jupyter-themes加载机制
在开始实现自定义加载进度条之前,我们需要先了解jupyter-themes的加载机制。jupyter-themes是一个用于自定义Jupyter Notebook主题的工具,它通过修改Jupyter Notebook的CSS样式文件来实现主题的切换和自定义。
jupyter-themes的加载过程主要包括以下几个步骤:
- 主题选择与参数配置:用户通过命令行或配置文件选择主题,并设置字体、颜色等参数。
- 资源编译与生成:jupyter-themes根据用户的选择和配置,编译LESS文件生成CSS样式,并复制所需的字体文件到Jupyter Notebook的自定义目录。
- 样式应用:Jupyter Notebook加载生成的CSS样式文件,应用新的主题。
从上面的加载过程可以看出,主题加载的主要耗时在于资源的编译与生成,特别是LESS文件的编译和字体文件的复制。因此,我们可以通过监控这些关键步骤的进度来实现加载进度条。
实现步骤:一步步打造自定义加载进度条
步骤一:创建加载进度条HTML结构
首先,我们需要在Jupyter Notebook的界面中添加一个加载进度条的HTML结构。我们可以通过修改Jupyter Notebook的自定义CSS和JavaScript文件来实现这一点。
打开Jupyter Notebook的自定义CSS文件(通常位于~/.jupyter/custom/custom.css),添加以下CSS代码:
/* 加载进度条样式 */
#theme-loading-progress {
position: fixed;
top: 0;
left: 0;
height: 3px;
background-color: #4CAF50;
z-index: 9999;
transition: width 0.3s ease;
}
#theme-loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
#theme-loading-message {
font-size: 18px;
color: #333;
font-family: "Source Code Pro", monospace;
}
接下来,打开Jupyter Notebook的自定义JavaScript文件(通常位于~/.jupyter/custom/custom.js),添加以下JavaScript代码来创建加载进度条的HTML结构:
// 创建加载进度条元素
function createLoadingProgress() {
// 创建进度条元素
const progressBar = document.createElement('div');
progressBar.id = 'theme-loading-progress';
progressBar.style.width = '0%';
// 创建遮罩层元素
const overlay = document.createElement('div');
overlay.id = 'theme-loading-overlay';
// 创建加载消息元素
const message = document.createElement('div');
message.id = 'theme-loading-message';
message.textContent = '加载主题中... 0%';
// 将消息元素添加到遮罩层
overlay.appendChild(message);
// 将进度条和遮罩层添加到页面
document.body.appendChild(progressBar);
document.body.appendChild(overlay);
}
步骤二:修改jupyter-themes源代码,添加进度回调
要实现进度条的实时更新,我们需要修改jupyter-themes的源代码,在关键的加载步骤添加进度回调函数。jupyter-themes的主要逻辑位于jupyterthemes/stylefx.py文件中。
首先,我们需要在stylefx.py文件中添加一个进度回调函数的参数。打开stylefx.py文件,找到install_theme函数的定义,添加一个progress_callback参数:
def install_theme(theme=None,
monofont=None,
monosize=11,
nbfont=None,
nbfontsize=13,
tcfont=None,
tcfontsize=13,
dffontsize=93,
outfontsize=85,
mathfontsize=100,
margins='auto',
cellwidth='980',
lineheight=170,
cursorwidth=2,
cursorcolor='default',
altprompt=False,
altmd=False,
altout=False,
hideprompt=False,
vimext=False,
toolbar=False,
nbname=False,
kernellogo=False,
dfonts=False,
keepcustom=False,
progress_callback=None): # 添加进度回调参数
接下来,我们需要在主题加载的关键步骤调用进度回调函数。例如,在删除旧字体文件、复制新字体文件、编译LESS文件等步骤后,我们可以更新进度并调用回调函数:
def install_theme(...):
# ... 其他代码 ...
# 删除旧字体文件
if progress_callback:
progress_callback(10, "正在清理旧字体文件...")
delete_font_files()
# 复制新字体文件
if monofont is not None and not dfonts:
if progress_callback:
progress_callback(20, "正在复制等宽字体文件...")
# ... 复制字体文件的代码 ...
if nbfont is not None and not dfonts and nbfont != 'proxima':
if progress_callback:
progress_callback(40, "正在复制笔记本字体文件...")
# ... 复制字体文件的代码 ...
# 编译LESS文件
if progress_callback:
progress_callback(60, "正在编译主题样式...")
style_less = ""
# ... 编译LESS文件的代码 ...
# 写入最终的CSS文件
if progress_callback:
progress_callback(80, "正在应用主题样式...")
write_final_css(style_css)
# 完成加载
if progress_callback:
progress_callback(100, "主题加载完成!")
步骤三:在前端实现进度更新逻辑
现在,我们需要在前端JavaScript中实现进度回调函数,实时更新进度条的显示。在custom.js文件中添加以下代码:
// 更新加载进度
function updateLoadingProgress(progress, message) {
const progressBar = document.getElementById('theme-loading-progress');
const messageElement = document.getElementById('theme-loading-message');
if (progressBar && messageElement) {
progressBar.style.width = `${progress}%`;
messageElement.textContent = `${message} ${progress}%`;
// 当进度达到100%时,隐藏进度条和遮罩层
if (progress >= 100) {
setTimeout(() => {
progressBar.style.display = 'none';
document.getElementById('theme-loading-overlay').style.display = 'none';
}, 1000);
}
}
}
// 初始化进度条并开始加载主题
function initThemeLoading() {
// 创建进度条
createLoadingProgress();
// 调用jupyter-themes的install_theme函数,并传入进度回调
// 注意:这里需要根据实际情况修改调用方式,可能需要通过Python桥接或其他方式调用
install_theme(
// ... 主题参数 ...
progress_callback: (progress, message) => {
// 在前端更新进度
updateLoadingProgress(progress, message);
}
);
}
// 当页面加载完成后初始化进度条
window.addEventListener('load', initThemeLoading);
步骤四:测试与优化
完成上述步骤后,我们需要测试自定义加载进度条的效果。启动Jupyter Notebook,尝试切换主题,观察进度条是否能够正确显示加载进度。
如果发现进度条显示不正常或者进度更新不及时,我们可能需要调整进度回调的时机和进度值的计算方式。例如,我们可以根据实际的文件数量和大小来动态计算复制字体文件的进度,而不是使用固定的进度值。
此外,我们还可以对进度条的样式进行优化,例如添加动画效果、调整颜色和字体等,以使其更符合当前主题的风格。
高级技巧:自定义进度条样式与动画
自定义进度条颜色
我们可以根据当前主题的颜色方案来自定义进度条的颜色。例如,对于深色主题,我们可以使用明亮的颜色作为进度条的颜色;对于浅色主题,我们可以使用深色的颜色。
在custom.css文件中添加以下代码:
/* 根据主题类型自定义进度条颜色 */
body[data-theme="dark"] #theme-loading-progress {
background-color: #4CAF50;
}
body[data-theme="light"] #theme-loading-progress {
background-color: #2196F3;
}
然后,在JavaScript中根据当前主题设置data-theme属性:
// 设置主题类型
function setThemeType(theme) {
if (theme === 'grade3' || theme === 'oceans16' || theme === 'chesterish' || theme === 'onedork' || theme === 'monokai') {
document.body.setAttribute('data-theme', 'dark');
} else {
document.body.setAttribute('data-theme', 'light');
}
}
添加进度条动画效果
我们可以为进度条添加动画效果,使其在更新进度时更加平滑。在custom.css文件中添加以下代码:
/* 添加进度条动画 */
#theme-loading-progress {
transition: width 0.3s ease-in-out;
}
#theme-loading-overlay {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
#theme-loading-overlay.active {
opacity: 1;
}
然后,在JavaScript中控制遮罩层的显示和隐藏:
// 显示遮罩层
function showOverlay() {
const overlay = document.getElementById('theme-loading-overlay');
if (overlay) {
overlay.classList.add('active');
}
}
// 隐藏遮罩层
function hideOverlay() {
const overlay = document.getElementById('theme-loading-overlay');
if (overlay) {
overlay.classList.remove('active');
}
}
常见问题与解决方案
问题一:进度条不显示
可能原因:
- 自定义CSS或JavaScript文件没有正确加载。
custom.js中的initThemeLoading函数没有被正确调用。- HTML元素的ID与CSS选择器不匹配。
解决方案:
- 检查Jupyter Notebook的自定义目录(通常位于
~/.jupyter/custom/)是否存在custom.css和custom.js文件。 - 在浏览器的开发者工具中查看控制台输出,检查是否有JavaScript错误。
- 确保HTML元素的ID(如
theme-loading-progress)与CSS选择器和JavaScript中的获取元素代码一致。
问题二:进度条进度不更新
可能原因:
stylefx.py中的进度回调函数没有被正确调用。- 进度值的计算方式不正确。
- 前端JavaScript中的
updateLoadingProgress函数存在错误。
解决方案:
- 在
stylefx.py中添加日志输出,检查进度回调函数是否被调用。 - 调整进度值的计算方式,确保进度能够正确反映实际的加载进度。
- 在浏览器的开发者工具中调试
updateLoadingProgress函数,检查参数是否正确传递。
问题三:进度条样式与主题不匹配
可能原因:
- 进度条的CSS样式没有根据主题进行调整。
- 主题切换后没有重新计算进度条的样式。
解决方案:
- 使用CSS变量或JavaScript动态计算进度条的样式,使其与当前主题保持一致。
- 在主题切换完成后,重新初始化进度条的样式。
总结与展望
通过本文的介绍,我们学习了如何为jupyter-themes添加自定义加载进度条,实现了主题加载进度的可视化。这不仅提升了用户体验,还让我们对jupyter-themes的加载机制有了更深入的了解。
未来,我们可以进一步优化进度条的实现,例如:
- 动态计算进度:根据实际的文件大小和数量动态计算加载进度,提高进度的准确性。
- 多语言支持:为加载消息添加多语言支持,满足不同用户的需求。
- 进度条样式模板:提供多种进度条样式模板,让用户可以根据自己的喜好选择。
希望本文能够帮助你更好地使用jupyter-themes,提升Jupyter Notebook的使用体验。如果你有任何问题或建议,欢迎在评论区留言讨论。
附录:完整代码示例
custom.css
/* 加载进度条样式 */
#theme-loading-progress {
position: fixed;
top: 0;
left: 0;
height: 3px;
background-color: #4CAF50;
z-index: 9999;
transition: width 0.3s ease-in-out;
}
#theme-loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
#theme-loading-overlay.active {
opacity: 1;
}
#theme-loading-message {
font-size: 18px;
color: #333;
font-family: "Source Code Pro", monospace;
}
/* 根据主题类型自定义进度条颜色 */
body[data-theme="dark"] #theme-loading-progress {
background-color: #4CAF50;
}
body[data-theme="light"] #theme-loading-progress {
background-color: #2196F3;
}
custom.js
// 创建加载进度条元素
function createLoadingProgress() {
// 如果进度条已存在,则不再创建
if (document.getElementById('theme-loading-progress')) {
return;
}
// 创建进度条元素
const progressBar = document.createElement('div');
progressBar.id = 'theme-loading-progress';
progressBar.style.width = '0%';
// 创建遮罩层元素
const overlay = document.createElement('div');
overlay.id = 'theme-loading-overlay';
// 创建加载消息元素
const message = document.createElement('div');
message.id = 'theme-loading-message';
message.textContent = '加载主题中... 0%';
// 将消息元素添加到遮罩层
overlay.appendChild(message);
// 将进度条和遮罩层添加到页面
document.body.appendChild(progressBar);
document.body.appendChild(overlay);
// 显示遮罩层
showOverlay();
}
// 更新加载进度
function updateLoadingProgress(progress, message) {
const progressBar = document.getElementById('theme-loading-progress');
const messageElement = document.getElementById('theme-loading-message');
if (progressBar && messageElement) {
progressBar.style.width = `${progress}%`;
messageElement.textContent = `${message} ${progress}%`;
// 当进度达到100%时,隐藏进度条和遮罩层
if (progress >= 100) {
setTimeout(() => {
progressBar.style.display = 'none';
hideOverlay();
}, 1000);
}
}
}
// 显示遮罩层
function showOverlay() {
const overlay = document.getElementById('theme-loading-overlay');
if (overlay) {
overlay.classList.add('active');
}
}
// 隐藏遮罩层
function hideOverlay() {
const overlay = document.getElementById('theme-loading-overlay');
if (overlay) {
overlay.classList.remove('active');
}
}
// 设置主题类型
function setThemeType(theme) {
if (theme === 'grade3' || theme === 'oceans16' || theme === 'chesterish' || theme === 'onedork' || theme === 'monokai') {
document.body.setAttribute('data-theme', 'dark');
} else {
document.body.setAttribute('data-theme', 'light');
}
}
// 初始化进度条并开始加载主题
function initThemeLoading() {
// 获取当前主题
const currentTheme = localStorage.getItem('currentTheme') || 'grade3';
setThemeType(currentTheme);
// 创建进度条
createLoadingProgress();
// 这里需要根据实际情况调用jupyter-themes的install_theme函数
// 并传入进度回调
// 注意:在实际应用中,可能需要通过Python桥接或其他方式调用
console.log('初始化主题加载进度条...');
}
// 当页面加载完成后初始化进度条
window.addEventListener('load', initThemeLoading);
stylefx.py(修改部分)
def install_theme(theme=None,
monofont=None,
monosize=11,
nbfont=None,
nbfontsize=13,
tcfont=None,
tcfontsize=13,
dffontsize=93,
outfontsize=85,
mathfontsize=100,
margins='auto',
cellwidth='980',
lineheight=170,
cursorwidth=2,
cursorcolor='default',
altprompt=False,
altmd=False,
altout=False,
hideprompt=False,
vimext=False,
toolbar=False,
nbname=False,
kernellogo=False,
dfonts=False,
keepcustom=False,
progress_callback=None): # 添加进度回调参数
# ... 其他代码 ...
# 删除旧字体文件
if progress_callback:
progress_callback(10, "正在清理旧字体文件...")
delete_font_files()
# 复制等宽字体文件
font_count = 0
total_fonts = 0
if monofont is not None and not dfonts:
monofont, monofpath = stored_font_dicts(monofont)
font_dir = os.path.join(fonts_dir, 'monospace', monofpath)
total_fonts = len([f for f in os.listdir(font_dir) if not f.endswith('.txt')])
if monofont is not None and not dfonts:
for fontfile in os.listdir(font_dir):
if '.txt' in fontfile or 'DS_' in fontfile:
continue
send_fonts_to_jupyter(os.path.join(font_dir, fontfile))
font_count += 1
if progress_callback:
progress = 20 + int((font_count / total_fonts) * 20)
progress_callback(progress, "正在复制等宽字体文件...")
# 复制笔记本字体文件
font_count = 0
total_fonts = 0
if nbfont is not None and not dfonts and nbfont != 'proxima':
nbfont, nbfontpath = stored_font_dicts(nbfont)
font_dir = os.path.join(fonts_dir, 'sans-serif', nbfontpath)
total_fonts = len([f for f in os.listdir(font_dir) if not f.endswith('.txt')])
if nbfont is not None and not dfonts and nbfont != 'proxima':
for fontfile in os.listdir(font_dir):
if '.txt' in fontfile or 'DS_' in fontfile:
continue
send_fonts_to_jupyter(os.path.join(font_dir, fontfile))
font_count += 1
if progress_callback:
progress = 40 + int((font_count / total_fonts) * 20)
progress_callback(progress, "正在复制笔记本字体文件...")
# 编译LESS文件
if progress_callback:
progress_callback(60, "正在编译主题样式...")
style_less = ""
# ... 编译LESS文件的代码 ...
# 写入最终的CSS文件
if progress_callback:
progress_callback(80, "正在应用主题样式...")
write_final_css(style_css)
# 完成加载
if progress_callback:
progress_callback(100, "主题加载完成!")
通过以上步骤,我们成功实现了jupyter-themes的自定义加载进度条,让主题加载过程更加透明和可控。这个功能不仅提升了用户体验,还展示了如何通过修改开源项目的源代码来添加自定义功能的方法。希望本文对你有所帮助!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



