From 68864a93a86727ccdcb9dc3d3f77f29ed36f96ea Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 29 May 2022 10:12:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E8=87=AA=E5=8B=95=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AF=E3=81=8A=E3=82=88=E3=81=B3dfn=E8=A6=81=E7=B4=A0?= =?UTF-8?q?=E3=81=AE=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/kunai/site/article.css | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/css/kunai/site/article.css b/css/kunai/site/article.css index 6745b51..9efa2c7 100644 --- a/css/kunai/site/article.css +++ b/css/kunai/site/article.css @@ -238,6 +238,28 @@ div[itemtype="http://schema.org/Article"] { border: none; } } + + dfn { + font-weight: bold; + } + a.cpprefjp-defined-word { + text-decoration: underline dotted 2px #08F; + text-underline-offset: 1px; + color: inherit; + } + a.cpprefjp-defined-word:link:hover { + background-color: #DDD; + } + a.cpprefjp-defined-word[data-desc] { + position: relative; + } + a.cpprefjp-defined-word[data-desc]:hover:after { + text-decoration: none!important; font-weight: normal!important; font-style: normal!important; + position: absolute; left: 2em; left: min(2em, 100%); top: 110%; width: max-content; max-width: 40em; z-index: 1000000; + border: 1px solid black; padding: 0.1em 0.4em; + background-color: white; color: black; text-decoration: none; font-size: 0.85rem; + content: attr(data-desc); + } } } From 6eff2f6fa2a412599a98e5e8adefb36279dfa461 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 18 Jun 2022 20:57:03 +0900 Subject: [PATCH 2/3] =?UTF-8?q?ui/content:=20=E3=83=84=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=81=E3=83=83=E3=83=97=E3=82=92=E3=82=B9=E3=82=AF=E3=83=AA?= =?UTF-8?q?=E3=83=97=E3=83=88=E3=81=A7=E5=AE=9F=E7=8F=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/kunai/site/article.css | 29 ++++++++++++----- js/kunai/ui/content.js | 67 +++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/css/kunai/site/article.css b/css/kunai/site/article.css index 9efa2c7..b481ccf 100644 --- a/css/kunai/site/article.css +++ b/css/kunai/site/article.css @@ -250,15 +250,8 @@ div[itemtype="http://schema.org/Article"] { a.cpprefjp-defined-word:link:hover { background-color: #DDD; } - a.cpprefjp-defined-word[data-desc] { - position: relative; - } - a.cpprefjp-defined-word[data-desc]:hover:after { - text-decoration: none!important; font-weight: normal!important; font-style: normal!important; - position: absolute; left: 2em; left: min(2em, 100%); top: 110%; width: max-content; max-width: 40em; z-index: 1000000; - border: 1px solid black; padding: 0.1em 0.4em; - background-color: white; color: black; text-decoration: none; font-size: 0.85rem; - content: attr(data-desc); + a.cpprefjp-defined-word[data-desc]:not(:link) { + cursor: context-menu; } } } @@ -318,3 +311,21 @@ div[itemtype="http://schema.org/Article"] { } } +/* Note: The tooltip element will be directly added to the top level of */ +#cpprefjp-dfn-tooltip { + text-decoration: none!important; font-weight: normal!important; font-style: normal!important; + width: max-content; max-width: 40em; + border: 1px solid black; padding: 0.2rem 0.4rem; margin: 0; + background-color: white; color: black; text-decoration: none; font-size: 0.9rem; + position: fixed; box-shadow: 2px 2px 2px 0 rgba(128, 128, 128, 0.6); + cursor: default; + opacity: 0; z-index: -1; + transition: opacity .3s linear .5s, z-index 0s linear .8s; +} +#cpprefjp-dfn-tooltip:before { + content: attr(data-desc); +} +#cpprefjp-dfn-tooltip.cpprefjp-dfn-tooltip-revealed { + opacity: 1; z-index: 1000000; + transition: opacity .3s linear 0s, z-index 0s linear 0s; +} diff --git a/js/kunai/ui/content.js b/js/kunai/ui/content.js index 81fb428..496fb5c 100644 --- a/js/kunai/ui/content.js +++ b/js/kunai/ui/content.js @@ -7,8 +7,73 @@ class Content { this.log.debug('initialzing...') this.log.debug(`found ${Badge.sanitize($('main[role="main"] div[itemtype="http://schema.org/Article"] .content-body span.cpp'))} badges`) + + this.setupTooltip() + } + + setupTooltip() { + const HORIZONTAL_MARGIN = 8 // (設定) ツールチップ配置時のビューポート横余白 + const VERTICAL_MARGIN = 8 // (設定) ツールチップ配置時のビューポート縦余白 + const VERTICAL_OFFSET = 2 // (設定) ツールチップと対象要素の縦の距離 + const TOOLTIP_ID = 'cpprefjp-dfn-tooltip' + const TOOLTIP_CLASS_REVEALED = 'cpprefjp-dfn-tooltip-revealed' + + const span = document.createElement('span') + span.id = TOOLTIP_ID + document.body.appendChild(span) + + let target = null + const showTooltipAt = (x, y, targetElement) => { + target = targetElement + + // 物理ピクセル位置にぴったり合わせる + x = Math.round(x * window.devicePixelRatio) / window.devicePixelRatio + y = Math.round(y * window.devicePixelRatio) / window.devicePixelRatio + + span.style.left = `${x}px` + span.style.top = `${y}px` + span.classList.add(TOOLTIP_CLASS_REVEALED) + } + const hideTooltip = () => { + target = null + span.classList.remove(TOOLTIP_CLASS_REVEALED) + } + + $('a[data-desc]').on({ + mouseover: function(e) { + // 幾何情報の取得 + span.dataset.desc = this.dataset.desc + const rect = this.getBoundingClientRect() // ツールチップ表示対象要素の矩形 + const vw = document.documentElement.clientWidth // スクロールバーを除くビューポートの幅 + const vh = document.documentElement.clientHeight // スクロールバーを除くビューポートの高さ + const mx = e.clientX // ビューポート内のマウス位置X + const my = e.clientY // ビューポート内のマウス位置Y + const tw = span.offsetWidth // ツールチップの表示幅 + const th = span.offsetHeight // ツールチップの表示高さ + + // 位置の決定 + let x = Math.max(HORIZONTAL_MARGIN, Math.min(vw - tw - HORIZONTAL_MARGIN, mx)) + let y = rect.top - VERTICAL_OFFSET - th + if (y < VERTICAL_MARGIN) { + y = rect.bottom + VERTICAL_OFFSET + if (y + th > vh - VERTICAL_MARGIN) y = my + VERTICAL_OFFSET + } + showTooltipAt(x, y, this) + }, + mouseout: function() { if (this === target) hideTooltip() } + }) + + const checkScroll = function(e) { + if (target === null) return + const rect = target.getBoundingClientRect() + const hitResult = + rect.left <= e.clientX && e.clientX <= rect.right && + rect.top <= e.clientY && e.clientY <= rect.bottom + if (!hitResult) hideTooltip() + } + window.addEventListener('scroll', checkScroll, true) + window.addEventListener('resize', checkScroll) } } export {Content} - From c9bc0c7378244a8e983a98728d59a687647b330f Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 22 Jun 2022 17:22:22 +0900 Subject: [PATCH 3/3] =?UTF-8?q?ui/tooltip:=20=E3=83=84=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=81=E3=83=83=E3=83=97=E8=A1=A8=E7=A4=BA=E9=83=A8=E5=88=86?= =?UTF-8?q?=E3=82=92=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/kunai/site.css | 1 + css/kunai/site/article.css | 19 ---------- css/kunai/site/tooltip.css | 18 ++++++++++ js/kunai/ui/content.js | 72 ++++++++++++-------------------------- js/kunai/ui/tooltip.js | 72 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 css/kunai/site/tooltip.css create mode 100644 js/kunai/ui/tooltip.js diff --git a/css/kunai/site.css b/css/kunai/site.css index 435263d..0c5226d 100644 --- a/css/kunai/site.css +++ b/css/kunai/site.css @@ -5,4 +5,5 @@ @import './site/sidebar.css'; @import './site/article.css'; @import './site/goog.css'; +@import './site/tooltip.css'; diff --git a/css/kunai/site/article.css b/css/kunai/site/article.css index b481ccf..281ab9c 100644 --- a/css/kunai/site/article.css +++ b/css/kunai/site/article.css @@ -310,22 +310,3 @@ div[itemtype="http://schema.org/Article"] { } } } - -/* Note: The tooltip element will be directly added to the top level of */ -#cpprefjp-dfn-tooltip { - text-decoration: none!important; font-weight: normal!important; font-style: normal!important; - width: max-content; max-width: 40em; - border: 1px solid black; padding: 0.2rem 0.4rem; margin: 0; - background-color: white; color: black; text-decoration: none; font-size: 0.9rem; - position: fixed; box-shadow: 2px 2px 2px 0 rgba(128, 128, 128, 0.6); - cursor: default; - opacity: 0; z-index: -1; - transition: opacity .3s linear .5s, z-index 0s linear .8s; -} -#cpprefjp-dfn-tooltip:before { - content: attr(data-desc); -} -#cpprefjp-dfn-tooltip.cpprefjp-dfn-tooltip-revealed { - opacity: 1; z-index: 1000000; - transition: opacity .3s linear 0s, z-index 0s linear 0s; -} diff --git a/css/kunai/site/tooltip.css b/css/kunai/site/tooltip.css new file mode 100644 index 0000000..4b293db --- /dev/null +++ b/css/kunai/site/tooltip.css @@ -0,0 +1,18 @@ +/* Note: The tooltip element will be directly added to the top level of */ +#kunai-ui-tooltip { + text-decoration: none!important; font-weight: normal!important; font-style: normal!important; + width: max-content; max-width: 40em; + border: 1px solid black; padding: 0.2rem 0.4rem; margin: 0; + background-color: white; color: black; text-decoration: none; font-size: 0.9rem; + position: fixed; box-shadow: 2px 2px 2px 0 rgba(128, 128, 128, 0.6); + cursor: default; + opacity: 0; z-index: -1; + transition: opacity .3s linear .5s, z-index 0s linear .8s; +} +#kunai-ui-tooltip:before { + content: attr(data-desc); +} +#kunai-ui-tooltip.kunai-ui-tooltip-revealed { + opacity: 1; z-index: 1000000; + transition: opacity .3s linear 0s, z-index 0s linear 0s; +} diff --git a/js/kunai/ui/content.js b/js/kunai/ui/content.js index 496fb5c..537a6d0 100644 --- a/js/kunai/ui/content.js +++ b/js/kunai/ui/content.js @@ -1,6 +1,14 @@ import * as Badge from './badge' +import {Tooltip} from './tooltip' +const _hitElementRects = (elem, x, y) => { + for (const rect of elem.getClientRects()) + if (rect.left <= x && x <= rect.right && rect.top <= y && y <= rect.bottom) + return rect + return null +} + class Content { constructor(log) { this.log = log.makeContext('Content') @@ -12,64 +20,30 @@ class Content { } setupTooltip() { - const HORIZONTAL_MARGIN = 8 // (設定) ツールチップ配置時のビューポート横余白 - const VERTICAL_MARGIN = 8 // (設定) ツールチップ配置時のビューポート縦余白 - const VERTICAL_OFFSET = 2 // (設定) ツールチップと対象要素の縦の距離 - const TOOLTIP_ID = 'cpprefjp-dfn-tooltip' - const TOOLTIP_CLASS_REVEALED = 'cpprefjp-dfn-tooltip-revealed' - - const span = document.createElement('span') - span.id = TOOLTIP_ID - document.body.appendChild(span) - + const tooltip = new Tooltip(document) let target = null - const showTooltipAt = (x, y, targetElement) => { - target = targetElement - - // 物理ピクセル位置にぴったり合わせる - x = Math.round(x * window.devicePixelRatio) / window.devicePixelRatio - y = Math.round(y * window.devicePixelRatio) / window.devicePixelRatio - - span.style.left = `${x}px` - span.style.top = `${y}px` - span.classList.add(TOOLTIP_CLASS_REVEALED) - } - const hideTooltip = () => { - target = null - span.classList.remove(TOOLTIP_CLASS_REVEALED) - } $('a[data-desc]').on({ mouseover: function(e) { - // 幾何情報の取得 - span.dataset.desc = this.dataset.desc - const rect = this.getBoundingClientRect() // ツールチップ表示対象要素の矩形 - const vw = document.documentElement.clientWidth // スクロールバーを除くビューポートの幅 - const vh = document.documentElement.clientHeight // スクロールバーを除くビューポートの高さ - const mx = e.clientX // ビューポート内のマウス位置X - const my = e.clientY // ビューポート内のマウス位置Y - const tw = span.offsetWidth // ツールチップの表示幅 - const th = span.offsetHeight // ツールチップの表示高さ - - // 位置の決定 - let x = Math.max(HORIZONTAL_MARGIN, Math.min(vw - tw - HORIZONTAL_MARGIN, mx)) - let y = rect.top - VERTICAL_OFFSET - th - if (y < VERTICAL_MARGIN) { - y = rect.bottom + VERTICAL_OFFSET - if (y + th > vh - VERTICAL_MARGIN) y = my + VERTICAL_OFFSET + const rect = _hitElementRects(this, e.clientX, e.clientY) + if (rect) { + target = this + tooltip.show(this.dataset.desc, e.clientX, e.clientY, rect) } - showTooltipAt(x, y, this) }, - mouseout: function() { if (this === target) hideTooltip() } + mouseout: function() { + if (this === target) { + target = null + tooltip.hide() + } + } }) const checkScroll = function(e) { - if (target === null) return - const rect = target.getBoundingClientRect() - const hitResult = - rect.left <= e.clientX && e.clientX <= rect.right && - rect.top <= e.clientY && e.clientY <= rect.bottom - if (!hitResult) hideTooltip() + if (target !== null && !_hitElementRects(target, e.clientX, e.clientY)) { + target = null + tooltip.hide() + } } window.addEventListener('scroll', checkScroll, true) window.addEventListener('resize', checkScroll) diff --git a/js/kunai/ui/tooltip.js b/js/kunai/ui/tooltip.js new file mode 100644 index 0000000..a8fa194 --- /dev/null +++ b/js/kunai/ui/tooltip.js @@ -0,0 +1,72 @@ +class Tooltip { + /** + * ツールチップを構築します。 + * @param {Document} [_document] - 表示対象のドキュメント + * @param {object} [config] - 設定 + */ + constructor(_document, config) { + this.document = _document || document + this.view = this.document.defaultView || window + this.config = { + horizontalMargin: 8, // (設定) ツールチップ配置時のビューポート横余白 + verticalMargin: 8, // (設定) ツールチップ配置時のビューポート縦余白 + verticalOffset: 2, // (設定) ツールチップと対象要素の縦の距離 + tooltipId: 'kunai-ui-tooltip', + tooltipClassRevealed: 'kunai-ui-tooltip-revealed' + } + if (config) + Object.assign(this.config, config) + + this.span = document.createElement('span') + this.span.id = this.config.tooltipId + this.document.body.appendChild(this.span) + } + + _place(x, y) { + // 物理ピクセル位置にぴったり合わせる + const pixelRatio = this.view.devicePixelRatio + x = Math.round(x * pixelRatio) / pixelRatio + y = Math.round(y * pixelRatio) / pixelRatio + + this.span.style.left = `${x}px` + this.span.style.top = `${y}px` + this.span.classList.add(this.config.tooltipClassRevealed) + } + + /** + * マウス位置および対象領域を元にして、ツールチップを適切な位置に表示します。 + * @param {string} desc - 表示する文字列 + * @param {number} mouseX - ビューポート内のマウス位置X + * @param {number} mouseY - ビューポート内のマウス位置Y + * @param {DOMRect} rect - 表示対象オブジェクトの領域 + * + */ + show(desc, mouseX, mouseY, rect) { + // 幾何情報の取得 + this.span.dataset.desc = desc + const tw = this.span.offsetWidth // ツールチップの表示幅 + const th = this.span.offsetHeight // ツールチップの表示高さ + const vw = this.document.documentElement.clientWidth // スクロールバーを除くビューポートの幅 + const vh = this.document.documentElement.clientHeight // スクロールバーを除くビューポートの高さ + + // 位置の決定 + let x = Math.max(this.config.horizontalMargin, Math.min(vw - tw - this.config.horizontalMargin, mouseX)) + let y = rect.top - this.config.verticalOffset - th + if (y < this.config.verticalMargin) { + y = rect.bottom + this.config.verticalOffset + if (y + th > vh - this.config.verticalMargin) + y = mouseY + this.config.verticalOffset + } + + this._place(x, y) + } + + /** + * ツールチップを隠します。 + */ + hide() { + this.span.classList.remove(this.config.tooltipClassRevealed) + } +} + +export {Tooltip}