Skip to content

Commit c188016

Browse files
author
Sebi Nemeth
committed
redesign and optimize
1 parent 9dec65f commit c188016

File tree

5 files changed

+93
-64
lines changed

5 files changed

+93
-64
lines changed

dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export type TranslationMeta = {
88
type LiveTranslatorPluginOptions = {
99
translationLink: (meta: TranslationMeta) => string;
1010
persist?: boolean;
11+
root?: HTMLElement;
12+
refreshRate?: number;
1113
};
1214
export declare function encodeMessages(messagesObject: any): any;
1315
export declare const LiveTranslatorPlugin: {

dist/index.js

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@ const css = `
2525
border-radius: 100%;
2626
background-color: red;
2727
}
28-
.live-translator-badge-wrapper {
29-
position: relative !important;
30-
width: 0px;
31-
height: 0px;
32-
}
3328
.live-translator-badge-container {
3429
position: absolute !important;
30+
background: rgba(255,255,255,0.5);
31+
outline: solid 2px rgba(255,255,255,0.5);
32+
border-radius: 5px;
3533
display: flex;
34+
gap: 2px;
3635
z-index: 10000;
3736
}
3837
.live-translator-badge {
@@ -60,17 +59,16 @@ const css = `
6059
box-shadow: 0px 0px 5px #00c0ff !important;
6160
}
6261
.live-translator-box {
63-
outline: solid 2px green;
64-
background: green;
65-
opacity: 0.1;
62+
outline: solid 2px lightgreen !important;
63+
box-shadow: 0px 0px 5px lightgreen !important;
6664
position: absolute;
67-
border-radius: 4px;
65+
border-radius: 7px;
6866
z-index: 9999;
6967
display: none;
7068
}
7169
.live-translator-box.attribute {
72-
outline: solid 2px blue;
73-
background: blue;
70+
outline: solid 2px #00c0ff !important;
71+
box-shadow: 0px 0px 5px #00c0ff !important;
7472
}
7573
`;
7674
function deepForIn(object, fn) {
@@ -157,6 +155,7 @@ class LiveTranslatorManager {
157155
_enableButton;
158156
_indicator;
159157
_box;
158+
_wrapper;
160159
constructor(options) {
161160
this._enabled = false;
162161
this._options = options;
@@ -184,13 +183,16 @@ class LiveTranslatorManager {
184183
this.render();
185184
});
186185
document.body.appendChild(this._enableButton);
186+
this._wrapper = document.createElement('div');
187+
this._wrapper.classList.add('live-translator-wrapper');
188+
document.body.prepend(this._wrapper);
187189
this._box = document.createElement('div');
188190
this._box.classList.add('live-translator-box');
189-
document.body.appendChild(this._box);
191+
this._wrapper.appendChild(this._box);
190192
// initialize encode
191193
// encode is moved to i18n.ts file
192194
// initialize decode & render
193-
const throttler = throttle(() => this.render(), 800);
195+
const throttler = throttle(() => this.render(), this._options.refreshRate || 50);
194196
const observer = new MutationObserver(throttler);
195197
observer.observe(document.documentElement, {
196198
subtree: true,
@@ -199,10 +201,13 @@ class LiveTranslatorManager {
199201
childList: false,
200202
});
201203
document.documentElement.addEventListener('mousemove', throttler);
202-
window.setInterval(throttler, 1000);
204+
window.setInterval(throttler, (this._options.refreshRate || 50) * 2);
203205
// render for the first time
204206
this.render();
205207
}
208+
get root() {
209+
return this._options.root || document.documentElement;
210+
}
206211
toggle(enable) {
207212
if (enable !== undefined) {
208213
this._enabled = enable;
@@ -216,22 +221,25 @@ class LiveTranslatorManager {
216221
console.log(`%c Live Translator ${this._enabled ? 'ON' : 'OFF'} `, 'background: #222; color: #bada55');
217222
}
218223
render() {
219-
const badgeWrappers = document.querySelectorAll('.live-translator-badge-wrapper');
224+
if (this._enabled) {
225+
console.time();
226+
}
220227
this._box.style.display = 'none';
221-
badgeWrappers.forEach((wrapper) => {
222-
wrapper.remove();
228+
document.
229+
querySelectorAll('.live-translator-badge-container').
230+
forEach((elem) => {
231+
elem.remove();
223232
});
224233
this._indicator.style.background = this._enabled ? 'lightgreen' : 'red';
225234
if (!this._enabled) {
226235
return;
227236
}
228237
const re = new RegExp(ZeroWidthEncoder.PATTERN, 'gm');
229-
const queue = [document.documentElement];
238+
const queue = [this.root];
230239
while (queue.length > 0) {
231240
const node = queue.pop();
232241
const badges = [];
233242
const parent = node.parentElement;
234-
const rect = getBoundingClientRect(node);
235243
if (node instanceof Text) {
236244
const matches = node.textContent.match(re);
237245
for (const match of matches ?? []) {
@@ -253,21 +261,22 @@ class LiveTranslatorManager {
253261
}
254262
}
255263
if (badges.length) {
256-
let container;
257-
if (node.previousElementSibling && node.previousElementSibling.classList.contains('live-translator-badge-container')) {
258-
container = node.previousElementSibling;
264+
let position = { top: 0, left: 0 };
265+
if (node instanceof Text) {
266+
const clientRect = getBoundingClientRect(node);
267+
position.top = clientRect.top + window.scrollY;
268+
position.left = clientRect.left + window.screenX;
259269
}
260270
else {
261-
const parentRect = getBoundingClientRect(node instanceof Text ? parent : node);
262-
container = document.createElement('span');
263-
container.classList.add('live-translator-badge-container');
264-
container.style.top = rect.top - parentRect.top + 'px';
265-
container.style.left = rect.left - parentRect.left + 'px';
266-
const relativeWrapper = document.createElement('span');
267-
relativeWrapper.classList.add('live-translator-badge-wrapper');
268-
relativeWrapper.appendChild(container);
269-
parent.insertBefore(relativeWrapper, node);
271+
const clientRect = node.getClientRects()[0];
272+
position.top = clientRect.top + clientRect.height - 10 + window.scrollY;
273+
position.left = clientRect.left + window.screenX;
270274
}
275+
const container = document.createElement('span');
276+
container.classList.add('live-translator-badge-container');
277+
container.style.top = position.top + 'px';
278+
container.style.left = position.left + 'px';
279+
this._wrapper.appendChild(container);
271280
for (const badge of badges) {
272281
container.appendChild(badge);
273282
}
@@ -276,6 +285,7 @@ class LiveTranslatorManager {
276285
queue.push(child);
277286
}
278287
}
288+
console.timeEnd();
279289
}
280290
showBox(node, attribute = false) {
281291
const rect = !attribute ? getBoundingClientRect(node) : node.getClientRects()[0];

src/demo/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</p>
4343
<h3>Scrollable container</h3>
4444
<div class="scroll">
45-
<div class="item" v-for="i in 5">{{ t('LTPlugin.ListItemN', [i]) }}</div>
45+
<div class="item translated" v-for="i in 5">{{ t('LTPlugin.ListItemN', [i]) }}</div>
4646
</div>
4747
<h3>Attribute</h3>
4848
<img class="image" src="https://source.unsplash.com/random/500x500" :alt="t('LTPlugin.Attrs.ImageAlt')"

src/demo/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ app.use(LiveTranslatorPlugin, {
1212
return `?meta=${encodeURIComponent(JSON.stringify(meta))}`
1313
},
1414
persist: true,
15+
root: document.getElementById('app'),
16+
refreshRate: 200,
1517
})
1618

1719
app.mount('#app')

src/index.ts

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ const css = `
2626
border-radius: 100%;
2727
background-color: red;
2828
}
29-
.live-translator-badge-wrapper {
30-
position: relative !important;
31-
width: 0px;
32-
height: 0px;
33-
}
3429
.live-translator-badge-container {
3530
position: absolute !important;
31+
background: rgba(255,255,255,0.5);
32+
outline: solid 2px rgba(255,255,255,0.5);
33+
border-radius: 5px;
3634
display: flex;
35+
gap: 2px;
3736
z-index: 10000;
3837
}
3938
.live-translator-badge {
@@ -61,17 +60,16 @@ const css = `
6160
box-shadow: 0px 0px 5px #00c0ff !important;
6261
}
6362
.live-translator-box {
64-
outline: solid 2px green;
65-
background: green;
66-
opacity: 0.1;
63+
outline: solid 2px lightgreen !important;
64+
box-shadow: 0px 0px 5px lightgreen !important;
6765
position: absolute;
68-
border-radius: 4px;
66+
border-radius: 7px;
6967
z-index: 9999;
7068
display: none;
7169
}
7270
.live-translator-box.attribute {
73-
outline: solid 2px blue;
74-
background: blue;
71+
outline: solid 2px #00c0ff !important;
72+
box-shadow: 0px 0px 5px #00c0ff !important;
7573
}
7674
`
7775
export type TranslationMeta = {
@@ -85,6 +83,8 @@ export type TranslationMeta = {
8583
type LiveTranslatorPluginOptions = {
8684
translationLink: (meta: TranslationMeta) => string
8785
persist?: boolean
86+
root?: HTMLElement
87+
refreshRate?: number
8888
}
8989

9090
function deepForIn(object: Object, fn: (value: string, key: string) => void) {
@@ -179,6 +179,7 @@ class LiveTranslatorManager {
179179
_indicator: HTMLSpanElement
180180

181181
_box: HTMLDivElement
182+
_wrapper: HTMLDivElement
182183

183184
constructor (options: LiveTranslatorPluginOptions) {
184185
this._enabled = false
@@ -212,15 +213,19 @@ class LiveTranslatorManager {
212213
})
213214
document.body.appendChild(this._enableButton)
214215

216+
this._wrapper = document.createElement('div')
217+
this._wrapper.classList.add('live-translator-wrapper')
218+
document.body.prepend(this._wrapper)
219+
215220
this._box = document.createElement('div')
216221
this._box.classList.add('live-translator-box')
217-
document.body.appendChild(this._box)
222+
this._wrapper.appendChild(this._box)
218223

219224
// initialize encode
220225
// encode is moved to i18n.ts file
221226

222227
// initialize decode & render
223-
const throttler = throttle(() => this.render(), 800)
228+
const throttler = throttle(() => this.render(), this._options.refreshRate || 50)
224229
const observer = new MutationObserver(throttler)
225230
observer.observe(document.documentElement,
226231
{
@@ -231,12 +236,16 @@ class LiveTranslatorManager {
231236
},
232237
)
233238
document.documentElement.addEventListener('mousemove', throttler)
234-
window.setInterval(throttler, 1000)
239+
window.setInterval(throttler, (this._options.refreshRate || 50) * 2)
235240

236241
// render for the first time
237242
this.render()
238243
}
239244

245+
get root (): HTMLElement {
246+
return this._options.root || document.documentElement
247+
}
248+
240249
toggle (enable?: boolean) {
241250
if (enable !== undefined) {
242251
this._enabled = enable
@@ -250,11 +259,15 @@ class LiveTranslatorManager {
250259
}
251260

252261
render () {
253-
const badgeWrappers = document.querySelectorAll('.live-translator-badge-wrapper')
262+
if (this._enabled) {
263+
console.time()
264+
}
254265
this._box.style.display = 'none'
255-
badgeWrappers.forEach((wrapper) => {
256-
wrapper.remove()
257-
})
266+
document.
267+
querySelectorAll('.live-translator-badge-container').
268+
forEach((elem) => {
269+
elem.remove()
270+
})
258271

259272
this._indicator.style.background = this._enabled ? 'lightgreen' : 'red'
260273

@@ -264,14 +277,13 @@ class LiveTranslatorManager {
264277

265278
const re = new RegExp(ZeroWidthEncoder.PATTERN, 'gm')
266279

267-
const queue = [document.documentElement] as Node[]
280+
const queue = [this.root] as Node[]
268281
while (queue.length > 0) {
269282
const node = queue.pop() as HTMLElement
270283

271284
const badges = [] as HTMLElement[]
272285
const parent = node.parentElement as HTMLElement
273286

274-
const rect = getBoundingClientRect(node)
275287
if (node instanceof Text) {
276288
const matches = (node.textContent as string).match(re)
277289
for (const match of matches ?? []) {
@@ -295,20 +307,22 @@ class LiveTranslatorManager {
295307
}
296308

297309
if (badges.length) {
298-
let container: HTMLElement
299-
if (node.previousElementSibling && node.previousElementSibling.classList.contains('live-translator-badge-container')) {
300-
container = node.previousElementSibling as HTMLElement
310+
let position = { top: 0, left: 0}
311+
if (node instanceof Text) {
312+
const clientRect = getBoundingClientRect(node)
313+
position.top = clientRect.top + window.scrollY
314+
position.left = clientRect.left + window.screenX
301315
} else {
302-
const parentRect = getBoundingClientRect(node instanceof Text ? parent : node);
303-
container = document.createElement('span')
304-
container.classList.add('live-translator-badge-container')
305-
container.style.top = rect.top - parentRect.top + 'px'
306-
container.style.left = rect.left - parentRect.left + 'px'
307-
const relativeWrapper = document.createElement('span')
308-
relativeWrapper.classList.add('live-translator-badge-wrapper')
309-
relativeWrapper.appendChild(container)
310-
parent.insertBefore(relativeWrapper, node)
316+
const clientRect = node.getClientRects()[0]
317+
position.top = clientRect.top + clientRect.height - 10 + window.scrollY
318+
position.left = clientRect.left + window.screenX
311319
}
320+
const container = document.createElement('span')
321+
container.classList.add('live-translator-badge-container')
322+
container.style.top = position.top + 'px'
323+
container.style.left = position.left + 'px'
324+
this._wrapper.appendChild(container)
325+
312326
for (const badge of badges) {
313327
container.appendChild(badge)
314328
}
@@ -318,6 +332,7 @@ class LiveTranslatorManager {
318332
queue.push(child)
319333
}
320334
}
335+
console.timeEnd()
321336
}
322337

323338
showBox(node: Node, attribute = false) {

0 commit comments

Comments
 (0)