Merge pull request #224 from toasted-nutbread/display-jquery-optimizations

Remove jQuery usage from Display.js
This commit is contained in:
Alex Yatskov 2019-09-28 09:12:23 -07:00 committed by GitHub
commit 64eed33e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 90 deletions

View File

@ -19,20 +19,27 @@
class DisplaySearch extends Display { class DisplaySearch extends Display {
constructor() { constructor() {
super($('#spinner'), $('#content')); super(document.querySelector('#spinner'), document.querySelector('#content'));
this.optionsContext = { this.optionsContext = {
depth: 0, depth: 0,
url: window.location.href url: window.location.href
}; };
this.search = $('#search').click(this.onSearch.bind(this)); this.search = document.querySelector('#search');
this.query = $('#query').on('input', this.onSearchInput.bind(this)); this.query = document.querySelector('#query');
this.intro = $('#intro'); this.intro = document.querySelector('#intro');
this.introHidden = false;
this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract});
window.wanakana.bind(this.query.get(0)); if (this.search !== null) {
this.search.addEventListener('click', (e) => this.onSearch(e), false);
}
if (this.query !== null) {
this.query.addEventListener('input', () => this.onSearchInput(), false);
window.wanakana.bind(this.query);
}
} }
onError(error) { onError(error) {
@ -40,23 +47,50 @@ class DisplaySearch extends Display {
} }
onSearchClear() { onSearchClear() {
this.query.focus().select(); if (this.query === null) {
return;
}
this.query.focus();
this.query.select();
} }
onSearchInput() { onSearchInput() {
this.search.prop('disabled', this.query.val().length === 0); this.search.disabled = (this.query === null || this.query.value.length === 0);
} }
async onSearch(e) { async onSearch(e) {
if (this.query === null) {
return;
}
try { try {
e.preventDefault(); e.preventDefault();
this.intro.slideUp(); this.hideIntro();
const {length, definitions} = await apiTermsFind(this.query.val(), this.optionsContext); const {length, definitions} = await apiTermsFind(this.query.value, this.optionsContext);
super.termsShow(definitions, await apiOptionsGet(this.optionsContext)); super.termsShow(definitions, await apiOptionsGet(this.optionsContext));
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} }
} }
hideIntro() {
if (this.introHidden) {
return;
}
this.introHidden = true;
if (this.intro === null) {
return;
}
const size = this.intro.getBoundingClientRect();
this.intro.style.height = `${size.height}px`;
this.intro.style.transition = 'height 0.4s ease-in-out 0s';
window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation
this.intro.style.height = '0';
}
} }
window.yomichan_search = new DisplaySearch(); window.yomichan_search = new DisplaySearch();

View File

@ -10,21 +10,19 @@
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<div id="intro"> <div id="intro" style="overflow: hidden;">
<div class="page-header"> <div class="page-header">
<h1>Yomichan Search</h1> <h1>Yomichan Search</h1>
</div> </div>
<p>Search your installed dictionaries by entering a Japanese expression into the field below.</p> <p style="margin-bottom: 0;">Search your installed dictionaries by entering a Japanese expression into the field below.</p>
</div> </div>
<p> <form class="input-group" style="padding-top: 10px;">
<form class="input-group"> <input type="text" class="form-control" placeholder="Search for..." id="query" autofocus>
<input type="text" class="form-control" placeholder="Search for..." id="query" autofocus> <span class="input-group-btn">
<span class="input-group-btn"> <input type="submit" class="btn btn-default form-control" id="search" value="Search" disabled>
<input type="submit" class="btn btn-default form-control" id="search" value="Search" disabled> </span>
</span> </form>
</form>
</p>
<div id="spinner"> <div id="spinner">
<img src="/mixed/img/spinner.gif"> <img src="/mixed/img/spinner.gif">
@ -34,7 +32,6 @@
</div> </div>
<script src="/mixed/lib/handlebars.min.js"></script> <script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/jquery.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script> <script src="/mixed/lib/wanakana.min.js"></script>
<script src="/mixed/js/extension.js"></script> <script src="/mixed/js/extension.js"></script>
@ -49,6 +46,7 @@
<script src="/fg/js/source.js"></script> <script src="/fg/js/source.js"></script>
<script src="/mixed/js/display.js"></script> <script src="/mixed/js/display.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/scroll.js"></script>
<script src="/bg/js/search.js"></script> <script src="/bg/js/search.js"></script>
<script src="/bg/js/search-frontend.js"></script> <script src="/bg/js/search-frontend.js"></script>

View File

@ -31,7 +31,6 @@
</div> </div>
</div> </div>
<script src="/mixed/lib/jquery.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script> <script src="/mixed/lib/wanakana.min.js"></script>
<script src="/mixed/js/extension.js"></script> <script src="/mixed/js/extension.js"></script>
@ -41,6 +40,7 @@
<script src="/fg/js/document.js"></script> <script src="/fg/js/document.js"></script>
<script src="/fg/js/source.js"></script> <script src="/fg/js/source.js"></script>
<script src="/mixed/js/display.js"></script> <script src="/mixed/js/display.js"></script>
<script src="/mixed/js/scroll.js"></script>
<script src="/fg/js/float.js"></script> <script src="/fg/js/float.js"></script>

View File

@ -19,7 +19,7 @@
class DisplayFloat extends Display { class DisplayFloat extends Display {
constructor() { constructor() {
super($('#spinner'), $('#definitions')); super(document.querySelector('#spinner'), document.querySelector('#definitions'));
this.autoPlayAudioTimer = null; this.autoPlayAudioTimer = null;
this.styleNode = null; this.styleNode = null;
@ -30,7 +30,7 @@ class DisplayFloat extends Display {
this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract});
$(window).on('message', utilAsync(this.onMessage.bind(this))); window.addEventListener('message', (e) => this.onMessage(e), false);
} }
onError(error) { onError(error) {
@ -42,8 +42,16 @@ class DisplayFloat extends Display {
} }
onOrphaned() { onOrphaned() {
$('#definitions').hide(); const definitions = document.querySelector('#definitions');
$('#error-orphaned').show(); const errorOrphaned = document.querySelector('#error-orphaned');
if (definitions !== null) {
definitions.style.setProperty('display', 'none', 'important');
}
if (errorOrphaned !== null) {
errorOrphaned.style.setProperty('display', 'block', 'important');
}
} }
onSearchClear() { onSearchClear() {
@ -86,7 +94,7 @@ class DisplayFloat extends Display {
} }
}; };
const {action, params} = e.originalEvent.data; const {action, params} = e.data;
const handler = handlers[action]; const handler = handlers[action];
if (handler) { if (handler) {
handler(params); handler(params);

View File

@ -230,3 +230,7 @@ div.glossary-item.compact-glossary {
.info-output td { .info-output td {
text-align: right; text-align: right;
} }
.entry:not(.entry-current) .current {
display: none;
}

View File

@ -28,11 +28,14 @@ class Display {
this.index = 0; this.index = 0;
this.audioCache = {}; this.audioCache = {};
this.optionsContext = {}; this.optionsContext = {};
this.eventListeners = [];
this.dependencies = {}; this.dependencies = {};
$(document).keydown(this.onKeyDown.bind(this)); this.windowScroll = new WindowScroll();
$(document).on('wheel', this.onWheel.bind(this));
document.addEventListener('keydown', this.onKeyDown.bind(this));
document.addEventListener('wheel', this.onWheel.bind(this), {passive: false});
} }
onError(error) { onError(error) {
@ -52,12 +55,13 @@ class Display {
try { try {
e.preventDefault(); e.preventDefault();
const link = $(e.target); const link = e.target;
this.windowScroll.toY(0);
const context = { const context = {
source: { source: {
definitions: this.definitions, definitions: this.definitions,
index: Display.entryIndexFind(link), index: this.entryIndexFind(link),
scroll: $('html,body').scrollTop() scroll: this.windowScroll.y
} }
}; };
@ -67,7 +71,7 @@ class Display {
context.source.source = this.context.source; context.source.source = this.context.source;
} }
const kanjiDefs = await apiKanjiFind(link.text(), this.optionsContext); const kanjiDefs = await apiKanjiFind(link.textContent, this.optionsContext);
this.kanjiShow(kanjiDefs, this.options, context); this.kanjiShow(kanjiDefs, this.options, context);
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
@ -80,7 +84,7 @@ class Display {
const {docRangeFromPoint, docSentenceExtract} = this.dependencies; const {docRangeFromPoint, docSentenceExtract} = this.dependencies;
const clickedElement = $(e.target); const clickedElement = e.target;
const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options); const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options);
if (textSource === null) { if (textSource === null) {
return false; return false;
@ -102,11 +106,12 @@ class Display {
textSource.cleanup(); textSource.cleanup();
} }
this.windowScroll.toY(0);
const context = { const context = {
source: { source: {
definitions: this.definitions, definitions: this.definitions,
index: Display.entryIndexFind(clickedElement), index: this.entryIndexFind(clickedElement),
scroll: $('html,body').scrollTop() scroll: this.windowScroll.y
} }
}; };
@ -124,38 +129,38 @@ class Display {
onAudioPlay(e) { onAudioPlay(e) {
e.preventDefault(); e.preventDefault();
const link = $(e.currentTarget); const link = e.currentTarget;
const definitionIndex = Display.entryIndexFind(link); const entry = link.closest('.entry');
const expressionIndex = link.closest('.entry').find('.expression .action-play-audio').index(link); const definitionIndex = this.entryIndexFind(entry);
const expressionIndex = Display.indexOf(entry.querySelectorAll('.expression .action-play-audio'), link);
this.audioPlay(this.definitions[definitionIndex], expressionIndex); this.audioPlay(this.definitions[definitionIndex], expressionIndex);
} }
onNoteAdd(e) { onNoteAdd(e) {
e.preventDefault(); e.preventDefault();
const link = $(e.currentTarget); const link = e.currentTarget;
const index = Display.entryIndexFind(link); const index = this.entryIndexFind(link);
this.noteAdd(this.definitions[index], link.data('mode')); this.noteAdd(this.definitions[index], link.dataset.mode);
} }
onNoteView(e) { onNoteView(e) {
e.preventDefault(); e.preventDefault();
const link = $(e.currentTarget); const link = e.currentTarget;
const index = Display.entryIndexFind(link); apiNoteView(link.dataset.noteId);
apiNoteView(link.data('noteId'));
} }
onKeyDown(e) { onKeyDown(e) {
const noteTryAdd = mode => { const noteTryAdd = mode => {
const button = Display.adderButtonFind(this.index, mode); const button = this.adderButtonFind(this.index, mode);
if (button.length !== 0 && !button.hasClass('disabled')) { if (button !== null && !button.classList.contains('disabled')) {
this.noteAdd(this.definitions[this.index], mode); this.noteAdd(this.definitions[this.index], mode);
} }
}; };
const noteTryView = mode => { const noteTryView = mode => {
const button = Display.viewerButtonFind(this.index); const button = this.viewerButtonFind(this.index);
if (button.length !== 0 && !button.hasClass('disabled')) { if (button !== null && !button.classList.contains('disabled')) {
apiNoteView(button.data('noteId')); apiNoteView(button.dataset.noteId);
} }
}; };
@ -237,7 +242,8 @@ class Display {
80: /* p */ () => { 80: /* p */ () => {
if (e.altKey) { if (e.altKey) {
if ($('.entry').eq(this.index).data('type') === 'term') { const entry = this.getEntry(this.index);
if (entry !== null && entry.dataset.type === 'term') {
this.audioPlay(this.definitions[this.index], this.firstExpressionIndex); this.audioPlay(this.definitions[this.index], this.firstExpressionIndex);
} }
@ -259,13 +265,12 @@ class Display {
} }
onWheel(e) { onWheel(e) {
const event = e.originalEvent;
const handler = () => { const handler = () => {
if (event.altKey) { if (e.altKey) {
if (event.deltaY < 0) { // scroll up if (e.deltaY < 0) { // scroll up
this.entryScrollIntoView(this.index - 1, null, true); this.entryScrollIntoView(this.index - 1, null, true);
return true; return true;
} else if (event.deltaY > 0) { // scroll down } else if (e.deltaY > 0) { // scroll down
this.entryScrollIntoView(this.index + 1, null, true); this.entryScrollIntoView(this.index + 1, null, true);
return true; return true;
} }
@ -273,12 +278,14 @@ class Display {
}; };
if (handler()) { if (handler()) {
event.preventDefault(); e.preventDefault();
} }
} }
async termsShow(definitions, options, context) { async termsShow(definitions, options, context) {
try { try {
this.clearEventListeners();
if (!context || context.focus !== false) { if (!context || context.focus !== false) {
window.focus(); window.focus();
} }
@ -310,7 +317,7 @@ class Display {
} }
const content = await apiTemplateRender('terms.html', params); const content = await apiTemplateRender('terms.html', params);
this.container.html(content); this.container.innerHTML = content;
const {index, scroll} = context || {}; const {index, scroll} = context || {};
this.entryScrollIntoView(index || 0, scroll); this.entryScrollIntoView(index || 0, scroll);
@ -318,13 +325,13 @@ class Display {
this.autoPlayAudio(); this.autoPlayAudio();
} }
$('.action-add-note').click(this.onNoteAdd.bind(this)); this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this));
$('.action-view-note').click(this.onNoteView.bind(this)); this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this));
$('.action-play-audio').click(this.onAudioPlay.bind(this)); this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this));
$('.kanji-link').click(this.onKanjiLookup.bind(this)); this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this));
$('.source-term').click(this.onSourceTermView.bind(this)); this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this));
if (this.options.scanning.enablePopupSearch) { if (this.options.scanning.enablePopupSearch) {
$('.glossary-item').click(this.onTermLookup.bind(this)); this.addEventListeners('.glossary-item', 'click', this.onTermLookup.bind(this));
} }
await this.adderButtonUpdate(['term-kanji', 'term-kana'], sequence); await this.adderButtonUpdate(['term-kanji', 'term-kana'], sequence);
@ -335,6 +342,8 @@ class Display {
async kanjiShow(definitions, options, context) { async kanjiShow(definitions, options, context) {
try { try {
this.clearEventListeners();
if (!context || context.focus !== false) { if (!context || context.focus !== false) {
window.focus(); window.focus();
} }
@ -362,13 +371,13 @@ class Display {
} }
const content = await apiTemplateRender('kanji.html', params); const content = await apiTemplateRender('kanji.html', params);
this.container.html(content); this.container.innerHTML = content;
const {index, scroll} = context || {}; const {index, scroll} = context || {};
this.entryScrollIntoView(index || 0, scroll); this.entryScrollIntoView(index || 0, scroll);
$('.action-add-note').click(this.onNoteAdd.bind(this)); this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this));
$('.action-view-note').click(this.onNoteView.bind(this)); this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this));
$('.source-term').click(this.onSourceTermView.bind(this)); this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this));
await this.adderButtonUpdate(['kanji'], sequence); await this.adderButtonUpdate(['kanji'], sequence);
} catch (e) { } catch (e) {
@ -390,14 +399,13 @@ class Display {
for (let i = 0; i < states.length; ++i) { for (let i = 0; i < states.length; ++i) {
const state = states[i]; const state = states[i];
for (const mode in state) { for (const mode in state) {
const button = Display.adderButtonFind(i, mode); const button = this.adderButtonFind(i, mode);
if (state[mode]) { if (button === null) {
button.removeClass('disabled'); continue;
} else {
button.addClass('disabled');
} }
button.removeClass('pending'); button.classList.toggle('disabled', !state[mode]);
button.classList.remove('pending');
} }
} }
} catch (e) { } catch (e) {
@ -409,22 +417,29 @@ class Display {
index = Math.min(index, this.definitions.length - 1); index = Math.min(index, this.definitions.length - 1);
index = Math.max(index, 0); index = Math.max(index, 0);
$('.current').hide().eq(index).show(); const entryPre = this.getEntry(this.index);
if (entryPre !== null) {
entryPre.classList.remove('entry-current');
}
const container = $('html,body').stop(); const entry = this.getEntry(index);
const entry = $('.entry').eq(index); if (entry !== null) {
entry.classList.add('entry-current');
}
this.windowScroll.stop();
let target; let target;
if (scroll) { if (scroll) {
target = scroll; target = scroll;
} else { } else {
target = index === 0 ? 0 : entry.offset().top; target = index === 0 || entry === null ? 0 : Display.getElementTop(entry);
} }
if (smooth) { if (smooth) {
container.animate({scrollTop: target}, 200); this.windowScroll.animate(this.windowScroll.x, target, 200);
} else { } else {
container.scrollTop(target); this.windowScroll.toY(target);
} }
this.index = index; this.index = index;
@ -446,7 +461,7 @@ class Display {
async noteAdd(definition, mode) { async noteAdd(definition, mode) {
try { try {
this.spinner.show(); this.setSpinnerVisible(true);
const context = {}; const context = {};
if (this.noteUsesScreenshot()) { if (this.noteUsesScreenshot()) {
@ -459,21 +474,28 @@ class Display {
const noteId = await apiDefinitionAdd(definition, mode, context, this.optionsContext); const noteId = await apiDefinitionAdd(definition, mode, context, this.optionsContext);
if (noteId) { if (noteId) {
const index = this.definitions.indexOf(definition); const index = this.definitions.indexOf(definition);
Display.adderButtonFind(index, mode).addClass('disabled'); const adderButton = this.adderButtonFind(index, mode);
Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId); if (adderButton !== null) {
adderButton.classList.add('disabled');
}
const viewerButton = this.viewerButtonFind(index);
if (viewerButton !== null) {
viewerButton.classList.remove('pending', 'disabled');
viewerButton.dataset.noteId = noteId;
}
} else { } else {
throw 'Note could note be added'; throw 'Note could note be added';
} }
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} finally { } finally {
this.spinner.hide(); this.setSpinnerVisible(false);
} }
} }
async audioPlay(definition, expressionIndex) { async audioPlay(definition, expressionIndex) {
try { try {
this.spinner.show(); this.setSpinnerVisible(true);
const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex];
let url = await apiAudioGetUrl(expression, this.options.general.audioSource); let url = await apiAudioGetUrl(expression, this.options.general.audioSource);
@ -505,7 +527,7 @@ class Display {
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} finally { } finally {
this.spinner.hide(); this.setSpinnerVisible(false);
} }
} }
@ -542,6 +564,15 @@ class Display {
return apiForward('popupSetVisible', {visible}); return apiForward('popupSetVisible', {visible});
} }
setSpinnerVisible(visible) {
this.spinner.style.display = visible ? 'block' : '';
}
getEntry(index) {
const entries = this.container.querySelectorAll('.entry');
return index >= 0 && index < entries.length ? entries[index] : null;
}
static clozeBuild(sentence, source) { static clozeBuild(sentence, source) {
const result = { const result = {
sentence: sentence.text.trim() sentence: sentence.text.trim()
@ -556,19 +587,51 @@ class Display {
return result; return result;
} }
static entryIndexFind(element) { entryIndexFind(element) {
return $('.entry').index(element.closest('.entry')); const entry = element.closest('.entry');
return entry !== null ? Display.indexOf(this.container.querySelectorAll('.entry'), entry) : -1;
} }
static adderButtonFind(index, mode) { adderButtonFind(index, mode) {
return $('.entry').eq(index).find(`.action-add-note[data-mode="${mode}"]`); const entry = this.getEntry(index);
return entry !== null ? entry.querySelector(`.action-add-note[data-mode="${mode}"]`) : null;
} }
static viewerButtonFind(index) { viewerButtonFind(index) {
return $('.entry').eq(index).find('.action-view-note'); const entry = this.getEntry(index);
return entry !== null ? entry.querySelector('.action-view-note') : null;
} }
static delay(time) { static delay(time) {
return new Promise((resolve) => setTimeout(resolve, time)); return new Promise((resolve) => setTimeout(resolve, time));
} }
static indexOf(nodeList, node) {
for (let i = 0, ii = nodeList.length; i < ii; ++i) {
if (nodeList[i] === node) {
return i;
}
}
return -1;
}
addEventListeners(selector, type, listener, options) {
this.container.querySelectorAll(selector).forEach((node) => {
node.addEventListener(type, listener, options);
this.eventListeners.push([node, type, listener, options]);
});
}
clearEventListeners() {
for (const [node, type, listener, options] of this.eventListeners) {
node.removeEventListener(type, listener, options);
}
this.eventListeners = [];
}
static getElementTop(element) {
const elementRect = element.getBoundingClientRect();
const documentRect = document.documentElement.getBoundingClientRect();
return elementRect.top - documentRect.top;
}
} }

100
ext/mixed/js/scroll.js Normal file
View File

@ -0,0 +1,100 @@
/*
* Copyright (C) 2019 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class WindowScroll {
constructor() {
this.animationRequestId = null;
this.animationStartTime = 0;
this.animationStartX = 0;
this.animationStartY = 0;
this.animationEndTime = 0;
this.animationEndX = 0;
this.animationEndY = 0;
this.requestAnimationFrameCallback = (t) => this.onAnimationFrame(t);
}
toY(y) {
this.to(this.x, y);
}
toX(x) {
this.to(x, this.y);
}
to(x, y) {
this.stop();
window.scroll(x, y);
}
animate(x, y, time) {
this.animationStartX = this.x;
this.animationStartY = this.y;
this.animationStartTime = window.performance.now();
this.animationEndX = x;
this.animationEndY = y;
this.animationEndTime = this.animationStartTime + time;
this.animationRequestId = window.requestAnimationFrame(this.requestAnimationFrameCallback);
}
stop() {
if (this.animationRequestId === null) {
return;
}
window.cancelAnimationFrame(this.animationRequestId);
this.animationRequestId = null;
}
onAnimationFrame(time) {
if (time >= this.animationEndTime) {
window.scroll(this.animationEndX, this.animationEndY);
this.animationRequestId = null;
return;
}
const t = WindowScroll.easeInOutCubic((time - this.animationStartTime) / (this.animationEndTime - this.animationStartTime));
window.scroll(
WindowScroll.lerp(this.animationStartX, this.animationEndX, t),
WindowScroll.lerp(this.animationStartY, this.animationEndY, t)
);
this.animationRequestId = window.requestAnimationFrame(this.requestAnimationFrameCallback);
}
get x() {
return window.scrollX || window.pageXOffset;
}
get y() {
return window.scrollY || window.pageYOffset;
}
static easeInOutCubic(t) {
if (t < 0.5) {
return (4.0 * t * t * t);
} else {
t = 1.0 - t;
return 1.0 - (4.0 * t * t * t);
}
}
static lerp(start, end, percent) {
return (end - start) * percent + start;
}
}