Skip to content

Commit

Permalink
feat: Optimize rendering mechanism 🚀 (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
willnguyen1312 authored May 16, 2023
1 parent 703c9da commit 06d3f28
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-dogs-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zoom-image/core": minor
---

Optimize rendering mechanism 🚀
2 changes: 1 addition & 1 deletion packages/core/src/createZoomImageHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export function createZoomImageHover(container: HTMLElement, options: ZoomImageH
subscribe: store.subscribe,
getState: store.getState,
update: (newState: StateUpdate) => {
store.update(newState)
store.setState(newState)
},
}
}
62 changes: 37 additions & 25 deletions packages/core/src/createZoomImageWheel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,34 +81,46 @@ export function createZoomImageWheel(container: HTMLElement, options: ZoomImageW
}

function update(newState: StateUpdate) {
if (typeof newState.enable === "boolean") {
store.update({
enable: newState.enable,
})
}
store.batch(() => {
if (typeof newState.enable === "boolean" && newState.enable !== state.enable) {
store.setState({
enable: newState.enable,
})

if (!newState.enable) {
return
}
}

if (typeof newState.currentZoom === "number" && newState.currentZoom !== state.currentZoom) {
const newCurrentZoom = clamp(newState.currentZoom, 1, finalOptions.maxZoom)
const zoomPointX = container.clientWidth / 2
const zoomPointY = container.clientHeight / 2
if (typeof newState.currentZoom === "number" && newState.currentZoom !== state.currentZoom) {
const newCurrentZoom = clamp(newState.currentZoom, 1, finalOptions.maxZoom)

const zoomTargetX = (zoomPointX - state.currentPositionX) / state.currentZoom
const zoomTargetY = (zoomPointY - state.currentPositionY) / state.currentZoom
if (newCurrentZoom === state.currentZoom) {
return
}

state.currentPositionX = calculatePositionX(
-zoomTargetX * newCurrentZoom + zoomPointX,
newCurrentZoom,
)
state.currentPositionY = calculatePositionY(
-zoomTargetY * newCurrentZoom + zoomPointY,
newCurrentZoom,
)
const zoomPointX = container.clientWidth / 2
const zoomPointY = container.clientHeight / 2

store.update({
currentZoom: newCurrentZoom,
})
updateZoom()
}
const zoomTargetX = (zoomPointX - state.currentPositionX) / state.currentZoom
const zoomTargetY = (zoomPointY - state.currentPositionY) / state.currentZoom

state.currentPositionX = calculatePositionX(
-zoomTargetX * newCurrentZoom + zoomPointX,
newCurrentZoom,
)
state.currentPositionY = calculatePositionY(
-zoomTargetY * newCurrentZoom + zoomPointY,
newCurrentZoom,
)

store.setState({
currentZoom: newCurrentZoom,
})

updateZoom()
}
})
}

function processZoomWheel({ delta, x, y }: { delta: number; x: number; y: number }) {
Expand All @@ -133,7 +145,7 @@ export function createZoomImageWheel(container: HTMLElement, options: ZoomImageW
-zoomTargetY * newCurrentZoom + zoomPointY,
newCurrentZoom,
)
store.update({ currentZoom: newCurrentZoom })
store.setState({ currentZoom: newCurrentZoom })
}

function _onWheel(event: WheelEvent) {
Expand Down
54 changes: 47 additions & 7 deletions packages/core/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
export type Listener<TState> = (currentState: TState) => void

export function createStore<TState>(initialState: TState) {
let batching = false
const listeners = new Set<Listener<TState>>()
const state: TState = initialState
let state: TState = initialState
let prevState: TState | undefined

const setState = (updatedState: Partial<TState>) => {
if (batching && !prevState) {
prevState = { ...state }
}

const update = (updatedState: Partial<TState>) => {
// @ts-ignore
Object.assign(state, updatedState)
state = Object.assign(state, updatedState)

flush()
}

const flush = () => {
if (batching) return

if (!prevState) {
listeners.forEach((listener) => listener(state))
return
}

let hasChanged = false
for (const key in state) {
if (state[key] !== prevState[key]) {
hasChanged = true
break
}
}

prevState = undefined

if (!hasChanged) {
return
}

listeners.forEach((listener) => listener(state))
}

const batch = (cb: () => void) => {
batching = true
cb()
batching = false
flush()
}

const subscribe = (listener: Listener<TState>) => {
listeners.add(listener)
return () => listeners.delete(listener)
Expand All @@ -20,10 +59,11 @@ export function createStore<TState>(initialState: TState) {
const getState = () => state

return {
update,
subscribe,
cleanup,
getState,
setState,
batch,
}
}

Expand All @@ -46,14 +86,14 @@ export const makeImageCache = () => {

loadedImageSet.add(src)

store.update({ zoomedImgStatus: "loading" })
store.setState({ zoomedImgStatus: "loading" })

img.addEventListener("load", () => {
store.update({ zoomedImgStatus: "loaded" })
store.setState({ zoomedImgStatus: "loaded" })
})

img.addEventListener("error", () => {
store.update({ zoomedImgStatus: "error" })
store.setState({ zoomedImgStatus: "error" })
})
}

Expand Down
18 changes: 13 additions & 5 deletions packages/core/test/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@ describe("simple store", () => {
const unsubscribe = store.subscribe(listener2)

expect(store.getState()).toEqual({ count: 0 })
store.update({ count: 1 })
store.setState({ count: 1 })
expect(store.getState()).toEqual({ count: 1 })
expect(listener1).toHaveBeenCalledTimes(1)
expect(listener2).toHaveBeenCalledTimes(1)

unsubscribe()
store.update({ count: 2 })
store.setState({ count: 2 })
expect(store.getState()).toEqual({ count: 2 })
expect(listener1).toHaveBeenCalledTimes(2)
expect(listener2).toHaveBeenCalledTimes(1)

store.batch(() => {
store.setState({ count: 4 })
store.setState({ count: 5 })
store.setState({ count: 6 })
})
expect(store.getState()).toEqual({ count: 6 })
expect(listener1).toHaveBeenCalledTimes(3)

store.cleanup()
store.update({ count: 3 })
expect(store.getState()).toEqual({ count: 3 })
expect(listener1).toHaveBeenCalledTimes(2)
store.setState({ count: 7 })
expect(store.getState()).toEqual({ count: 7 })
expect(listener1).toHaveBeenCalledTimes(3)
expect(listener2).toHaveBeenCalledTimes(1)
})
})
Expand Down

0 comments on commit 06d3f28

Please sign in to comment.