Optimize canvas highlight rendering

This commit is contained in:
Aljaž Grilc 2025-02-14 09:36:26 +01:00
parent d773682eeb
commit 6fb6503e73

View File

@ -279,7 +279,6 @@ class BesService {
// Scroll panel is "position: absolute", we need to keep it aligned with the host element. // Scroll panel is "position: absolute", we need to keep it aligned with the host element.
this.scrollPanel.style.top = `${-this.hostElement.scrollTop}px` this.scrollPanel.style.top = `${-this.hostElement.scrollTop}px`
this.scrollPanel.style.left = `${-this.hostElement.scrollLeft}px` this.scrollPanel.style.left = `${-this.hostElement.scrollLeft}px`
if (this.hostElement !== this.textElement) { if (this.hostElement !== this.textElement) {
this.textElement.scrollTop = this.hostElement.scrollTop this.textElement.scrollTop = this.hostElement.scrollTop
this.textElement.scrollLeft = this.hostElement.scrollLeft this.textElement.scrollLeft = this.hostElement.scrollLeft
@ -332,44 +331,31 @@ class BesService {
*/ */
addMistakeMarkup(range, ruleId) { addMistakeMarkup(range, ruleId) {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect() const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
console.log(canvasPanelRect)
const highlights = [] const highlights = []
this.canvasPanel.classList.add('bes-canvas') this.canvasPanel.classList.add('bes-canvas')
const ctx = this.canvasPanel.getContext('2d') const ctx = this.canvasPanel.getContext('2d')
ctx.lineWidth = 2 ctx.lineWidth = 1
ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE') ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
? '#e91313' ? '#e91313'
: '#269b26' : '#269b26'
// Set the canvas dimensions and scale the context only once this.setCanvasDpr()
if (!this.canvasInitialized) {
const dpr = window.devicePixelRatio || 1
this.canvasPanel.width = canvasPanelRect.width * dpr
this.canvasPanel.height = canvasPanelRect.height * dpr
this.canvasPanel.style.width = `${canvasPanelRect.width}px`
this.canvasPanel.style.height = `${canvasPanelRect.height}px`
const ctx = this.canvasPanel.getContext('2d')
ctx.scale(dpr, dpr)
this.canvasInitialized = true
}
for (let rect of range.getClientRects()) { for (let rect of range.getClientRects()) {
console.log(rect)
const x = rect.left - canvasPanelRect.left const x = rect.left - canvasPanelRect.left
const y = rect.top - canvasPanelRect.top + rect.height const y = rect.top - canvasPanelRect.top + rect.height
const width = rect.width const width = rect.width
const height = rect.height
console.log('rect.left: ', rect.left) const globalX = rect.left + window.scrollX
console.log('canvasPanelRect.left: ', canvasPanelRect.left) const globalY = rect.top + window.scrollY
console.log('x: ', x)
console.log('y: ', y)
console.log('width: ', width)
// Draw the underline // Draw the underline
ctx.beginPath() ctx.beginPath()
ctx.moveTo(x, y) ctx.moveTo(x, y)
ctx.lineTo(x + width, y) ctx.lineTo(x + width, y)
ctx.stroke() ctx.stroke()
const rectCoords = { x, y, width, height, globalX, globalY }
highlights.push(rectCoords)
} }
return highlights return highlights
@ -410,7 +396,15 @@ class BesService {
* @returns * @returns
*/ */
static isPointInRect(x, y, rect) { static isPointInRect(x, y, rect) {
return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom const globalWidth = rect.globalX + rect.width
const globalHeight = rect.globalY + rect.height
return (
rect.globalX <= x &&
x < globalWidth &&
rect.globalY <= y &&
y < globalHeight
)
// return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom
} }
/** /**
@ -460,6 +454,7 @@ class BesService {
this.scrollPanel.style.height = `${this.hostElement.scrollHeight}px` this.scrollPanel.style.height = `${this.hostElement.scrollHeight}px`
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px` this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px` this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
this.setCanvasDpr()
if (this.isHostElementInline()) { if (this.isHostElementInline()) {
const totalWidth = const totalWidth =
parseFloat(styles.paddingLeft) + parseFloat(styles.paddingLeft) +
@ -475,6 +470,18 @@ class BesService {
} }
} }
/**
* Sets canvas panel device pixel ratio.
*/
setCanvasDpr() {
const ctx = this.canvasPanel.getContext('2d')
const dpr = window.devicePixelRatio || 1
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
this.canvasPanel.width = canvasPanelRect.width * dpr
this.canvasPanel.height = canvasPanelRect.height * dpr
ctx.scale(dpr, dpr)
}
/** /**
* Displays correction panel. * Displays correction panel.
* *
@ -496,12 +503,12 @@ class BesService {
* @param {*} match Grammar checking rule match * @param {*} match Grammar checking rule match
*/ */
highlightMistake(match) { highlightMistake(match) {
document.querySelectorAll('.bes-mistake-highlight-selected').forEach(el => { // document.querySelectorAll('.bes-mistake-highlight-selected').forEach(el => {
el.classList.remove('bes-mistake-highlight-selected') // el.classList.remove('bes-mistake-highlight-selected')
}) // })
match.highlights.forEach(h => // match.highlights.forEach(h =>
h.classList.add('bes-mistake-highlight-selected') // h.classList.add('bes-mistake-highlight-selected')
) // )
} }
/** /**
@ -522,9 +529,18 @@ class BesService {
* Updates all grammar mistake markup positions. * Updates all grammar mistake markup positions.
*/ */
repositionAllMarkup() { repositionAllMarkup() {
const ctx = this.canvasPanel.getContext('2d')
this.results.forEach(result => { this.results.forEach(result => {
result.matches.forEach(match => { result.matches.forEach(match => {
if (match.highlights) match.highlights.forEach(h => h.remove()) if (match.highlights) {
match.highlights.forEach(h => {
ctx.clearRect(h.x - 2, h.y - 2, h.width + 4, h.height)
})
delete match.highlights
// match.highlights.forEach(h => h.remove())
}
match.highlights = this.addMistakeMarkup( match.highlights = this.addMistakeMarkup(
match.range, match.range,
match.match.rule.id match.match.rule.id
@ -783,12 +799,20 @@ class BesTreeService extends BesService {
* @param {Element} el DOM element we want to clean markup for * @param {Element} el DOM element we want to clean markup for
*/ */
clearMarkup(el) { clearMarkup(el) {
const ctx = this.canvasPanel.getContext('2d')
this.results this.results
.filter(result => BesTreeService.isSameParagraph(result.element, el)) .filter(result => BesTreeService.isSameParagraph(result.element, el))
.forEach(result => .forEach(result =>
result.matches.forEach(match => { result.matches.forEach(match => {
console.log(match)
if (match.highlights) { if (match.highlights) {
match.highlights.forEach(h => h.remove()) // match.highlights.forEach(h => h.remove())
match.highlights.forEach(h => {
console.log(h)
// Figure out why i need to add 2px to width
ctx.clearRect(h.x - 2, h.y - 2, h.width + 4, h.height)
})
delete match.highlights delete match.highlights
} }
}) })
@ -932,13 +956,7 @@ class BesTreeService extends BesService {
for (let result of this.results) { for (let result of this.results) {
for (let m of result.matches) { for (let m of result.matches) {
for (let h of m.highlights) { for (let h of m.highlights) {
if ( if (BesService.isPointInRect(source.clientX, source.clientY, h)) {
BesService.isPointInRect(
source.clientX,
source.clientY,
h.getBoundingClientRect()
)
) {
this.popupCorrectionPanel(el, m, source) this.popupCorrectionPanel(el, m, source)
return return
} }