Add button when scrolled outside of visible area (#643)

With the infinite scroll behavior, it's easy to scroll super far away from where the content is and have a hard time getting back. This PR adds a button to refocus on the center of the scene when no elements are visible anymore.
This commit is contained in:
Christopher Chedeau 2020-02-01 16:52:10 +00:00 committed by GitHub
parent 7c9e6dd3f1
commit be97bd980e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 44 additions and 8 deletions

View File

@ -24,6 +24,7 @@ export function getDefaultAppState(): AppState {
scrollY: 0, scrollY: 0,
cursorX: 0, cursorX: 0,
cursorY: 0, cursorY: 0,
scrolledOutside: false,
name: DEFAULT_PROJECT_NAME, name: DEFAULT_PROJECT_NAME,
}; };
} }

View File

@ -36,6 +36,7 @@ import {
importFromBackend, importFromBackend,
addToLoadedScenes, addToLoadedScenes,
loadedScenes, loadedScenes,
calculateScrollCenter,
} from "./scene"; } from "./scene";
import { renderScene } from "./renderer"; import { renderScene } from "./renderer";
@ -1764,6 +1765,16 @@ export class App extends React.Component<any, AppState> {
currentLanguage={getLanguage()} currentLanguage={getLanguage()}
/> />
{this.renderIdsDropdown()} {this.renderIdsDropdown()}
{this.state.scrolledOutside && (
<button
className="scroll-back-to-content"
onClick={() => {
this.setState({ ...calculateScrollCenter(elements) });
}}
>
{t("buttons.scrollBackToContent")}
</button>
)}
</footer> </footer>
</div> </div>
); );
@ -1872,11 +1883,20 @@ export class App extends React.Component<any, AppState> {
}, 300); }, 300);
componentDidUpdate() { componentDidUpdate() {
renderScene(elements, this.rc!, this.canvas!, { const atLeastOneVisibleElement = renderScene(
scrollX: this.state.scrollX, elements,
scrollY: this.state.scrollY, this.rc!,
viewBackgroundColor: this.state.viewBackgroundColor, this.canvas!,
}); {
scrollX: this.state.scrollX,
scrollY: this.state.scrollY,
viewBackgroundColor: this.state.viewBackgroundColor,
},
);
const scrolledOutside = !atLeastOneVisibleElement && elements.length > 0;
if (this.state.scrolledOutside !== scrolledOutside) {
this.setState({ scrolledOutside: scrolledOutside });
}
this.saveDebounced(); this.saveDebounced();
if (history.isRecording()) { if (history.isRecording()) {
history.pushEntry( history.pushEntry(

View File

@ -52,7 +52,8 @@
"getShareableLink": "Get shareable link", "getShareableLink": "Get shareable link",
"close": "Close", "close": "Close",
"selectLanguage": "Select Language", "selectLanguage": "Select Language",
"previouslyLoadedScenes": "Previously loaded scenes" "previouslyLoadedScenes": "Previously loaded scenes",
"scrollBackToContent": "Scroll back to content"
}, },
"alerts": { "alerts": {
"clearReset": "This will clear the whole canvas. Are you sure?", "clearReset": "This will clear the whole canvas. Are you sure?",

View File

@ -32,7 +32,7 @@ export function renderScene(
renderSelection?: boolean; renderSelection?: boolean;
} = {}, } = {},
) { ) {
if (!canvas) return; if (!canvas) return false;
const context = canvas.getContext("2d")!; const context = canvas.getContext("2d")!;
const fillStyle = context.fillStyle; const fillStyle = context.fillStyle;
@ -57,6 +57,7 @@ export function renderScene(
scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY, scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY,
}; };
let atLeastOneVisibleElement = false;
elements.forEach(element => { elements.forEach(element => {
if ( if (
!isVisibleElement( !isVisibleElement(
@ -71,6 +72,7 @@ export function renderScene(
) { ) {
return; return;
} }
atLeastOneVisibleElement = true;
context.translate( context.translate(
element.x + sceneState.scrollX, element.x + sceneState.scrollX,
element.y + sceneState.scrollY, element.y + sceneState.scrollY,
@ -141,6 +143,8 @@ export function renderScene(
context.strokeStyle = strokeStyle; context.strokeStyle = strokeStyle;
context.fillStyle = fillStyle; context.fillStyle = fillStyle;
} }
return atLeastOneVisibleElement;
} }
function isVisibleElement( function isVisibleElement(

View File

@ -46,7 +46,7 @@ export function serializeAsJSON(
); );
} }
function calculateScrollCenter( export function calculateScrollCenter(
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
): { scrollX: number; scrollY: number } { ): { scrollX: number; scrollY: number } {
let [x1, y1, x2, y2] = getCommonBounds(elements); let [x1, y1, x2, y2] = getCommonBounds(elements);

View File

@ -17,6 +17,7 @@ export {
importFromBackend, importFromBackend,
addToLoadedScenes, addToLoadedScenes,
loadedScenes, loadedScenes,
calculateScrollCenter,
} from "./data"; } from "./data";
export { export {
hasBackground, hasBackground,

View File

@ -256,3 +256,11 @@ button,
clip: rect(1px, 1px, 1px, 1px); clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap; /* added line */ white-space: nowrap; /* added line */
} }
.scroll-back-to-content {
position: fixed;
left: 50%;
bottom: 20px;
transform: translateX(-50%);
padding: 10px 20px;
}

View File

@ -22,6 +22,7 @@ export type AppState = {
scrollY: number; scrollY: number;
cursorX: number; cursorX: number;
cursorY: number; cursorY: number;
scrolledOutside: boolean;
name: string; name: string;
selectedId?: string; selectedId?: string;
}; };