Skip to content

Commit

Permalink
feat: improve event listener
Browse files Browse the repository at this point in the history
  • Loading branch information
oct16 committed Mar 13, 2020
1 parent 045ce39 commit cdf359c
Show file tree
Hide file tree
Showing 19 changed files with 157 additions and 113 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const elementList: [HTMLElement, string][] = [
用户在网页中移动鼠标会产生很多`mouseMove`事件,通过 `const {x,y} = event.target` 获取到了轨迹的坐标与时间戳

假如我在页面上用鼠标划过一个💖的轨迹,可能会得到下图这样的坐标点
![heart1](./heart1.png)
![heart1](./assets/heart1.png)

但是对于录屏这个业务场景来说,大部分场合我们并不要求100%还原精确的鼠标轨迹,我门只会关心两种情况:
```
Expand All @@ -144,7 +144,7 @@ const elementList: [HTMLElement, string][] = [

那么通过这个两个策略对鼠标轨迹进行精简后,画一个💖大约只需要6个点,通过样条曲线来模拟鼠标的虚拟轨迹,当 t = 0.2 的时候,就可以得到一个下图这样带着弧度的轨迹了

![heart2](./heart2.png)
![heart2](./assets/heart2.png)

// TODO DETAIL

Expand All @@ -154,7 +154,7 @@ const elementList: [HTMLElement, string][] = [

这里需要注意的地方是当页面切换的时候我们需要重置热力图,如果是单页应用,通过 `History``popstate``hashchange` 可以监听页面的变化

![heatmap](./heatmap.png)
![heatmap](./assets/heatmap.png)

##### 对于用户隐私的脱敏

Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
19 changes: 19 additions & 0 deletions assets/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Replay</title>
</head>

<body>

<script type="module">
import { replay } from './web-replay.js'
replay()
</script>

</body>

</html>
23 changes: 19 additions & 4 deletions examples/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebReplay</title>


<style>
label {
margin-left: 10px;
Expand Down Expand Up @@ -83,10 +81,27 @@ <h2>Form:</h2>
i = 0
}
}

</script>
<script type="module">
import { record, DB } from './web-replay.js'
DB.then(db => {
db.clear()
const ctr = record({
emitter: data => {
db.add(data)
}
})

const replayButton = document.getElementById('replay')
if (replayButton) {
replayButton.onclick = () => {
window.open('/replay.html')
ctr.uninstall()
}
}
})

<script src="web-replay.js"></script>
</script>
</body>

</html>
2 changes: 1 addition & 1 deletion examples/todo.html
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ <h1>todos</h1>
app.$mount('.todoapp')

</script>
<script src="web-replay.js"></script>
<script type="module" src="web-replay.js"></script>
</body>

</html>
25 changes: 3 additions & 22 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,3 @@
import { record } from '@WebReplay/record'
import { replay } from '@WebReplay/player'
import { DBPromise, nodeStore } from '@WebReplay/utils'

async function start() {
const indexDB = await DBPromise

record({
emitter: data => {
indexDB.add(data)
}
})

const replayButton = document.getElementById('replay')
if (replayButton) {
replayButton.onclick = () => {
replay()
}
}
}

start()
export { record } from '@WebReplay/record'
export { replay } from '@WebReplay/player'
export { DBPromise as DB } from '@WebReplay/utils'
11 changes: 1 addition & 10 deletions packages/player/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Pointer } from './pointer'

export class Container {
container: HTMLElement
convertHTMLElement: HTMLElement
sandBox: HTMLIFrameElement
pointer: Pointer

Expand All @@ -21,7 +20,6 @@ export class Container {
}

init() {
this.renderHTML()
this.initTemplate()
this.initSandbox()
}
Expand All @@ -31,7 +29,7 @@ export class Container {
this.sandBox.style.width = this.width + 'px'
this.sandBox.style.height = this.height + 'px'
const sandBoxDoc = (this.sandBox.contentWindow as Window).document
sandBoxDoc.replaceChild(this.convertHTMLElement, sandBoxDoc.documentElement)
sandBoxDoc.replaceChild(convertVNode(this.vNode, null)!, sandBoxDoc.documentElement)
}

initTemplate() {
Expand All @@ -52,11 +50,4 @@ export class Container {
const style = parser.parseFromString(`<style>${STYLE}</style>`, 'text/html').head.firstChild as HTMLElement
return style
}

renderHTML() {
const html = convertVNode(this.vNode, null)
if (html) {
this.convertHTMLElement = html as HTMLElement
}
}
}
6 changes: 0 additions & 6 deletions packages/player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ export async function replay() {
const indexDB = await DBPromise
const { width, height, vNode, data } = await indexDB.getData()

document.documentElement.innerHTML = ''

Array.from(listenerStore.entries()).forEach(([name, handle]) => {
document.removeEventListener(name, handle)
})

const contain = new Container({
vNode,
width,
Expand Down
12 changes: 10 additions & 2 deletions packages/record/src/record.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { snapshot, SnapshotData } from '@WebReplay/snapshot'
import { snapshots, SnapshotData } from '@WebReplay/snapshot'
import { RecordOptions } from './types'
import { listenerStore } from '@WebReplay/utils'

const ctrl = {
uninstall: () => {
Array.from(listenerStore.values()).forEach(un => un())
}
}

export const record = ({ emitter }: RecordOptions = {}) => {
recordAll(emitter)
return ctrl
}

function recordAll(emitter?: (e: SnapshotData) => void) {
const recordTasks: Function[] = [...Object.values(snapshot)]
const recordTasks: Function[] = [...Object.values(snapshots)]

recordTasks.forEach(task => {
task(emitter)
Expand Down
135 changes: 86 additions & 49 deletions packages/snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,12 @@ function mouseObserve(emit: SnapshotEvent<MouseSnapshot>) {
const listenerHandle = throttle(evt, 100, {
trailing: true
})
listenerStore.set(name, listenerHandle)

document.addEventListener(name, listenerHandle)

listenerStore.add(() => {
document.removeEventListener(name, listenerHandle)
})
}

function mouseClick() {
Expand All @@ -82,7 +86,9 @@ function mouseObserve(emit: SnapshotEvent<MouseSnapshot>) {

const name = 'click'
const listenerHandle = throttle(evt, 250)
listenerStore.set(name, listenerHandle)
listenerStore.add(() => {
document.removeEventListener(name, listenerHandle)
})
document.addEventListener(name, listenerHandle)
}

Expand Down Expand Up @@ -119,7 +125,6 @@ function DOMObserve(emit: SnapshotEvent<DOMObserve>) {
break
case 'characterData':
const parent = target.parentNode!

joinData({
parentId: nodeStore.getNodeId(parent),
value: target.nodeValue,
Expand Down Expand Up @@ -193,71 +198,103 @@ function DOMObserve(emit: SnapshotEvent<DOMObserve>) {
childList: true,
subtree: true
})

listenerStore.add(() => {
observer.disconnect()
})
}

function formElementObserve(emit: SnapshotEvent<FormElementObserve>) {
const els = nodeStore.getAllInputs()
listenInputs(emit)
kidnapInputs(emit) // for sys write in input
}

listenInput(emit) // for sys write in input
function listenInputs(emit: SnapshotEvent<FormElementObserve>) {
const eventTypes = ['input', 'change', 'focus', 'blur']

els.forEach(el => {
el.addEventListener('input', (e: InputEvent) => {
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.INPUT,
id: nodeStore.getNodeId(e.target as Node)!,
value: (e.target as HTMLInputElement).value
},
time: Date.now().toString()
})
})
el.addEventListener('focus', (e: InputEvent) => {
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.FOCUS,
id: nodeStore.getNodeId(e.target as Node)!
},
time: Date.now().toString()
})
eventTypes
.map(type => {
return (fn: (e: InputEvent) => void) => {
document.addEventListener(type, fn, { passive: true, capture: true, once: false })
}
})
el.addEventListener('blur', (e: InputEvent) => {
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.BLUR,
id: nodeStore.getNodeId(e.target as Node)!
},
time: Date.now().toString()
})
.forEach(handle => handle(handleFn))

listenerStore.add(() => {
eventTypes.forEach(type => {
document.removeEventListener(type, handleFn)
})
})

function handleFn(e: InputEvent) {
const eventType = e.type

switch (eventType) {
case 'input':
case 'change':
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.INPUT,
id: nodeStore.getNodeId(e.target as Node)!,
value: (e.target as HTMLInputElement).value
},
time: Date.now().toString()
})
break
case 'focus':
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.FOCUS,
id: nodeStore.getNodeId(e.target as Node)!
},
time: Date.now().toString()
})
break
case 'blur':
emit({
type: SnapshotType.FORM_EL_UPDATE,
data: {
type: FormElementEvent.BLUR,
id: nodeStore.getNodeId(e.target as Node)!
},
time: Date.now().toString()
})
break
default:
break
}
}
}

function listenInput(emit: SnapshotEvent<FormElementObserve>) {
function kidnapInputs(emit: SnapshotEvent<FormElementObserve>) {
const elementList: [HTMLElement, string][] = [
[HTMLInputElement.prototype, 'value'],
[HTMLInputElement.prototype, 'checked'],
[HTMLSelectElement.prototype, 'value'],
[HTMLTextAreaElement.prototype, 'value']
]

elementList.forEach(item => {
const [target, key] = item
const original = Object.getOwnPropertyDescriptor(target, key)
Object.defineProperty(target, key, {
set: function(value: string | boolean) {
setTimeout(() => {
handleEvent.call(this, key, value)
})
if (original && original.set) {
original.set.call(this, value)
const handles = elementList.map(item => {
return () => {
const [target, key] = item
const original = Object.getOwnPropertyDescriptor(target, key)
Object.defineProperty(target, key, {
set: function(value: string | boolean) {
setTimeout(() => {
handleEvent.call(this, key, value)
})
if (original && original.set) {
original.set.call(this, value)
}
}
}
})
})
}
})

handles.concat([]).forEach(handle => handle())

function handleEvent(this: HTMLElement, key: string, value: string) {
emit({
type: SnapshotType.FORM_EL_UPDATE,
Expand All @@ -272,7 +309,7 @@ function listenInput(emit: SnapshotEvent<FormElementObserve>) {
}
}

export const snapshot = {
export const snapshots = {
windowSnapshot,
DOMSnapshot,
mouseObserve,
Expand Down
1 change: 1 addition & 0 deletions packages/snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum SnapshotType {
export enum FormElementEvent {
'ATTR' = 'ATTR',
'INPUT' = 'INPUT',
'CHANGE' = 'CHANGE',
'FOCUS' = 'FOCUS',
'BLUR' = 'BLUR'
}
Expand Down
Loading

0 comments on commit cdf359c

Please sign in to comment.