Perf: Improve text outline performance (#3429)
We use text shadows to create "outlines" around text shapes. These shadows are rendered on the GPU. In Chrome (and on computers with a capable GPU) text shadows work pretty well, however on Safari—and in particular on iOS—they cause massive frame drops. https://github.com/tldraw/tldraw/assets/23072548/b65cbcaa-6cc3-46f3-b54d-1f9cc07fc499 This PR: - adds an LOD to text shadows, removing them at < 35% zoom - removes text shadows entirely on Safari If we had a "high performance" or "low-end device" mode, then shadows / text shadows would be the first to go. ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `improvement` — Improving existing features ### Test Plan 1. Use text shapes on iOS. 2. Use text shapes on Safari. 3. Use text shapes on Chrome. ### Release Notes - Improves performance of text shapes on iOS / Safari.
This commit is contained in:
parent
6305e83830
commit
2bbab1a790
2 changed files with 33 additions and 2 deletions
|
@ -3,9 +3,10 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema'
|
||||||
import { dedupe, modulate, objectMapValues } from '@tldraw/utils'
|
import { dedupe, modulate, objectMapValues } from '@tldraw/utils'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { Fragment, JSX, useEffect, useRef, useState } from 'react'
|
import { Fragment, JSX, useEffect, useRef, useState } from 'react'
|
||||||
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../../constants'
|
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS, TEXT_SHADOW_LOD } from '../../constants'
|
||||||
import { useCanvasEvents } from '../../hooks/useCanvasEvents'
|
import { useCanvasEvents } from '../../hooks/useCanvasEvents'
|
||||||
import { useCoarsePointer } from '../../hooks/useCoarsePointer'
|
import { useCoarsePointer } from '../../hooks/useCoarsePointer'
|
||||||
|
import { useContainer } from '../../hooks/useContainer'
|
||||||
import { useDocumentEvents } from '../../hooks/useDocumentEvents'
|
import { useDocumentEvents } from '../../hooks/useDocumentEvents'
|
||||||
import { useEditor } from '../../hooks/useEditor'
|
import { useEditor } from '../../hooks/useEditor'
|
||||||
import { useEditorComponents } from '../../hooks/useEditorComponents'
|
import { useEditorComponents } from '../../hooks/useEditorComponents'
|
||||||
|
@ -37,6 +38,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
||||||
const rCanvas = useRef<HTMLDivElement>(null)
|
const rCanvas = useRef<HTMLDivElement>(null)
|
||||||
const rHtmlLayer = useRef<HTMLDivElement>(null)
|
const rHtmlLayer = useRef<HTMLDivElement>(null)
|
||||||
const rHtmlLayer2 = useRef<HTMLDivElement>(null)
|
const rHtmlLayer2 = useRef<HTMLDivElement>(null)
|
||||||
|
const container = useContainer()
|
||||||
|
|
||||||
useScreenBounds(rCanvas)
|
useScreenBounds(rCanvas)
|
||||||
useDocumentEvents()
|
useDocumentEvents()
|
||||||
|
@ -45,11 +47,37 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
||||||
useGestureEvents(rCanvas)
|
useGestureEvents(rCanvas)
|
||||||
useFixSafariDoubleTapZoomPencilEvents(rCanvas)
|
useFixSafariDoubleTapZoomPencilEvents(rCanvas)
|
||||||
|
|
||||||
|
const rMemoizedStuff = useRef({ lodDisableTextOutline: false, allowTextOutline: true })
|
||||||
|
|
||||||
useQuickReactor(
|
useQuickReactor(
|
||||||
'position layers',
|
'position layers',
|
||||||
function positionLayersWhenCameraMoves() {
|
function positionLayersWhenCameraMoves() {
|
||||||
const { x, y, z } = editor.getCamera()
|
const { x, y, z } = editor.getCamera()
|
||||||
|
|
||||||
|
// This should only run once on first load
|
||||||
|
if (rMemoizedStuff.current.allowTextOutline && editor.environment.isSafari) {
|
||||||
|
container.style.setProperty('--tl-text-outline', 'none')
|
||||||
|
rMemoizedStuff.current.allowTextOutline = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// And this should only run if we're not in Safari;
|
||||||
|
// If we're below the lod distance for text shadows, turn them off
|
||||||
|
if (
|
||||||
|
rMemoizedStuff.current.allowTextOutline &&
|
||||||
|
z < TEXT_SHADOW_LOD !== rMemoizedStuff.current.lodDisableTextOutline
|
||||||
|
) {
|
||||||
|
const lodDisableTextOutline = z < TEXT_SHADOW_LOD
|
||||||
|
container.style.setProperty(
|
||||||
|
'--tl-text-outline',
|
||||||
|
lodDisableTextOutline
|
||||||
|
? 'none'
|
||||||
|
: `0 var(--b) 0 var(--color-background), 0 var(--a) 0 var(--color-background),
|
||||||
|
var(--b) var(--b) 0 var(--color-background), var(--a) var(--b) 0 var(--color-background),
|
||||||
|
var(--a) var(--a) 0 var(--color-background), var(--b) var(--a) 0 var(--color-background)`
|
||||||
|
)
|
||||||
|
rMemoizedStuff.current.lodDisableTextOutline = lodDisableTextOutline
|
||||||
|
}
|
||||||
|
|
||||||
// Because the html container has a width/height of 1px, we
|
// Because the html container has a width/height of 1px, we
|
||||||
// need to create a small offset when zoomed to ensure that
|
// need to create a small offset when zoomed to ensure that
|
||||||
// the html container and svg container are lined up exactly.
|
// the html container and svg container are lined up exactly.
|
||||||
|
@ -62,7 +90,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
||||||
setStyleProperty(rHtmlLayer.current, 'transform', transform)
|
setStyleProperty(rHtmlLayer.current, 'transform', transform)
|
||||||
setStyleProperty(rHtmlLayer2.current, 'transform', transform)
|
setStyleProperty(rHtmlLayer2.current, 'transform', transform)
|
||||||
},
|
},
|
||||||
[editor]
|
[editor, container]
|
||||||
)
|
)
|
||||||
|
|
||||||
const events = useCanvasEvents()
|
const events = useCanvasEvents()
|
||||||
|
|
|
@ -107,3 +107,6 @@ export const HANDLE_RADIUS = 12
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const LONG_PRESS_DURATION = 500
|
export const LONG_PRESS_DURATION = 500
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const TEXT_SHADOW_LOD = 0.35
|
||||||
|
|
Loading…
Reference in a new issue