-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🎨 Improve tooltip #13476
🎨 Improve tooltip #13476
Conversation
- 优化元素更新 - 改变 hideTooltip() 的方式为添加类名 .fn__none - 增加 .tooltip--tab_header
This comment was marked as resolved.
This comment was marked as resolved.
改了一下 |
@@ -170,7 +170,7 @@ ${unicode2Emoji(item.unicode, undefined, false, true)}</button>`; | |||
window.siyuan.config.editor.emoji.forEach(emojiUnicode => { | |||
const emoji = recentEmojis.filter((item) => item.unicode === emojiUnicode); | |||
if (emoji[0]) { | |||
recentHTML += `<button data-unicode="${emoji[0].unicode}" class="emojis__item ariaLabel" aria-label="${getEmojiDesc(emoji[0])}"> | |||
recentHTML += `<button data-unicode="${emoji[0].unicode}" class="emojis__item ariaLabel" data-tooltipclass="emoji" aria-label="${getEmojiDesc(emoji[0])}"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data-tooltipclass="emoji"
的作用是给 tooltip 元素添加一个 .tooltip--emoji 的类名,思源没有对应的样式,因为这个是我做主题需要用的。
冲突我解决了。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这样侵入性太强了,而且不是通用的。能否使用其他方式?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
刚刚用 JS 实现了给特定元素的悬浮提示添加类名,有空再把这个 PR 里面不需要改的地方改回去 // 判断是否为手机
let isMobile;
(async () => {
// TODO跟进 https://github.com/siyuan-note/siyuan/issues/13952 如果支持了切换界面,需要在切换界面之后重新执行被跳过的程序
isMobile = !!document.getElementById("sidebar");
})();
// 获取包含特定属性和值的上层元素
// 属性值只能是 string 类型,if 条件优化掉了 `typeof value === "string" &&`
const hasClosestByAttribute = (e, attr, value) => {
// 到达 <html> 元素时,e.parentElement 会返回 null,跳出循环
while (e) {
if (e.getAttribute(attr)?.includes(value)) return e; // 找到匹配的元素,直接返回;为了提高性能,优化掉了 .split(" ")
e = e.parentElement; // 继续向上查找
}
return false; // 未找到匹配的元素
};
// 把类名添加到 tooltip 元素的 classList 中
const addClass2Tooltip = (tooltipClass) => {
// 类名不存在才添加类名
// 感觉理论上会有 tooltip 显示又隐藏了才添加类名的情况,但实际没测出来。不过即使 tooltip 隐藏了也要添加类名,因为可以有 .tooltip--custom.fn__none 的样式
if (!tooltipElement.classList.contains(tooltipClass)) {
tooltipElement.classList.add(tooltipClass);
}
};
// 判断元素是否需要添加类名
// 这个函数不能弄防抖,因为原生的 showTooltip() 没有防抖,会覆盖掉类名
let tooltipObserver;
const checkAndAddClassOnHover = (event) => {
tooltipObserver?.disconnect(); // 避免重复创建监听器
if (!event.target || event.target.nodeType === 9) return false;
const element = event.target.nodeType === 3 ? event.target.parentElement : event.target;
// 表情选择器上的表情、底部选项
if (element.classList.contains("emojis__item") || element.classList.contains("emojis__type")) {
addClass2Tooltip("tooltip--emoji");
return;
}
// 数据库资源字段中的链接、属性面板数据库资源字段中的链接
if (element.parentElement?.closest('[data-dtype="mAsset"]') || element.parentElement?.closest('[data-type="mAsset"]')) {
addClass2Tooltip("tooltip--href_av");
return;
}
// 页签
const tabHeaderElement = hasClosestByAttribute(element, "data-type", "tab-header");
if (tabHeaderElement) {
// 如果页签没有 aria-label 属性,说明 tooltip 也还没有被添加
// 要监听 tooltipElement 元素的类名变化,等 fn__none 类名被移除之后再调用 addClass2Tooltip() 和卸载监听
tooltipObserver = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (!tooltipElement.classList.contains('fn__none')) {
addClass2Tooltip("tooltip--tab_header");
tooltipObserver?.disconnect(); // 卸载监听
}
}
});
tooltipObserver.observe(tooltipElement, { attributeFilter: ['class'] });
// 先判断再监听,中间可能会有时间差,故改为先监听再判断
if (tabHeaderElement.hasAttribute("aria-label")) {
tooltipObserver?.disconnect(); // 卸载监听
addClass2Tooltip("tooltip--tab_header");
}
// return;
}
};
// 功能:鼠标悬浮在特定元素上时,给当前显示的 tooltip 添加类名
let tooltipElement;
(async () => {
if (isMobile) return;
tooltipElement = document.getElementById("tooltip");
if (tooltipElement) {
// 参考原生的 initBlockPopover 函数
document.addEventListener('mouseover', checkAndAddClassOnHover);
} else {
console.log("Whisper: tooltip element does not exist.");
}
})(); 更新:换了一种性能更好的方式,把添加类名换成添加属性 (function() {
// 判断是否为手机
let isMobile;
(async () => {
// TODO跟进 https://github.com/siyuan-note/siyuan/issues/13952 如果支持了切换界面,需要在切换界面之后重新执行被跳过的程序
isMobile = !!document.getElementById("sidebar");
})();
// 关闭或卸载主题
window.destroyTheme = () => {
document.removeEventListener('mouseover', updateTooltipData);
tooltipElement?.removeAttribute("data-whisper-tooltip");
}
const isLocalPath = (link) => {
if (!link) return false;
link = link.trim();
if (1 > link.length) return false;
link = link.toLowerCase();
if (link.startsWith("assets/") || link.startsWith("file://") || link.startsWith("\\\\") /* Windows 网络共享路径 */) {
return true;
}
const colonIdx = link.indexOf(":");
return 1 === colonIdx; // 冒号前面只有一个字符认为是 Windows 盘符而不是网络协议
};
// 给 tooltip 元素添加 data-whisper-tooltip 属性值
const setTooltipData = (data) => {
if (tooltipElement.dataset?.whisperTooltip !== data) {
tooltipElement.dataset.whisperTooltip = data;
}
};
// 判断元素是否需要添加特定属性。原生的 showTooltip() 会覆盖掉类名,改成添加 data-* 属性就不会冲突了
const updateTooltipData = (event) => {
if (!event.target || event.target.nodeType === 9) return;
const e = event.target.nodeType === 3 ? event.target.parentElement : event.target;
// 文本超链接
if (e.getAttribute("data-href")) {
// 资源文件链接
if (isLocalPath(e.getAttribute("data-href"))) {
setTooltipData("href_asset");
return;
}
// 普通链接
setTooltipData("href");
return;
}
// 页签
if (e.parentElement?.closest('[data-type="tab-header"]') || e.closest('[data-type="tab-header"]')) {
setTooltipData("tab_header");
return;
}
// 数据库超链接
if (e.classList.contains("av__celltext--url")) {
setTooltipData("href_av");
return;
}
// 表情选择器上的表情、底部选项
if (e.classList.contains("emojis__item") || e.classList.contains("emojis__type")) {
setTooltipData("emoji");
return;
}
// 如果正在显示的 tooltip 不属于特定元素,就将属性置空
if (!tooltipElement.classList.contains("fn__none")) {
tooltipElement.dataset.whisperTooltip = "";
}
};
// 功能:鼠标悬浮在特定元素上时,给当前显示的 tooltip 添加特定属性
let tooltipElement;
(async () => {
if (isMobile) return;
tooltipElement = document.getElementById("tooltip");
if (tooltipElement) {
// 参考原生的 initBlockPopover 函数
document.addEventListener('mouseover', updateTooltipData);
} else {
// TODO跟进 PR 合并后才能用这个功能,不过没合并之前也不会有问题,会执行 else 分支 https://github.com/siyuan-note/siyuan/pull/13966
console.log("Whisper: tooltip element does not exist.");
}
})();
})(); |
重新改了:#13966 |
<div class="fn__hr"></div>
.fn__none 的用途是配合 CSS 和 JS 实现这样的效果:
video.webm
顺便分享一下主题用的对应代码片段:
更新:发现纯 CSS 就能实现,不过需要 tooltip 元素一开始就存在