Fixed scrollsync perf issue

This commit is contained in:
benweet 2014-05-23 09:02:07 +01:00
parent 33cdc60610
commit c0b1c9107d

View File

@ -1,272 +1,270 @@
define([ define([
"jquery", "jquery",
"underscore", "underscore",
"classes/Extension", "classes/Extension",
"text!html/scrollSyncSettingsBlock.html" "text!html/scrollSyncSettingsBlock.html"
], function($, _, Extension, scrollSyncSettingsBlockHTML) { ], function($, _, Extension, scrollSyncSettingsBlockHTML) {
var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true); var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true);
scrollSync.settingsBlock = scrollSyncSettingsBlockHTML; scrollSync.settingsBlock = scrollSyncSettingsBlockHTML;
$.easing.easeOutSine = function( p ) { var sectionList;
return Math.cos((1 - p) * Math.PI / 2 ); scrollSync.onSectionsCreated = function(sectionListParam) {
}; sectionList = sectionListParam;
};
var sectionList; var editorElt;
scrollSync.onSectionsCreated = function(sectionListParam) { var previewElt;
sectionList = sectionListParam; var mdSectionList = [];
}; var htmlSectionList = [];
var lastEditorScrollTop;
var lastPreviewScrollTop;
var buildSections = _.debounce(function() {
mdSectionList = [];
var mdSectionOffset;
var scrollHeight;
_.each(editorElt.querySelectorAll(".wmd-input-section"), function(delimiterElt) {
if(mdSectionOffset === undefined) {
// Force start to 0 for the first section
mdSectionOffset = 0;
return;
}
delimiterElt = delimiterElt.firstChild;
// Consider div scroll position
var newSectionOffset = delimiterElt.offsetTop;
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: newSectionOffset,
height: newSectionOffset - mdSectionOffset
});
mdSectionOffset = newSectionOffset;
});
// Last section
scrollHeight = editorElt.scrollHeight;
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - mdSectionOffset
});
var $editorElt; // Find corresponding sections in the preview
var $previewElt; htmlSectionList = [];
var mdSectionList = []; var htmlSectionOffset;
var htmlSectionList = []; _.each(previewElt.querySelectorAll(".wmd-preview-section"), function(delimiterElt) {
var lastEditorScrollTop; if(htmlSectionOffset === undefined) {
var lastPreviewScrollTop; // Force start to 0 for the first section
var buildSections = _.debounce(function() { htmlSectionOffset = 0;
mdSectionList = []; return;
var mdSectionOffset; }
var scrollHeight; // Consider div scroll position
var editorScrollTop = $editorElt.scrollTop(); var newSectionOffset = delimiterElt.offsetTop;
$editorElt.find(".wmd-input-section").each(function() { htmlSectionList.push({
if(mdSectionOffset === undefined) { startOffset: htmlSectionOffset,
// Force start to 0 for the first section endOffset: newSectionOffset,
mdSectionOffset = 0; height: newSectionOffset - htmlSectionOffset
return; });
} htmlSectionOffset = newSectionOffset;
var $delimiterElt = $(this.firstChild); });
// Consider div scroll position // Last section
var newSectionOffset = $delimiterElt.position().top + editorScrollTop; scrollHeight = previewElt.scrollHeight;
mdSectionList.push({ htmlSectionList.push({
startOffset: mdSectionOffset, startOffset: htmlSectionOffset,
endOffset: newSectionOffset, endOffset: scrollHeight,
height: newSectionOffset - mdSectionOffset height: scrollHeight - htmlSectionOffset
}); });
mdSectionOffset = newSectionOffset;
});
// Last section
scrollHeight = $editorElt.prop('scrollHeight');
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - mdSectionOffset
});
// Find corresponding sections in the preview // apply Scroll Link (-10 to have a gap > 9px)
htmlSectionList = []; lastEditorScrollTop = -10;
var htmlSectionOffset; lastPreviewScrollTop = -10;
var previewScrollTop = $previewElt.scrollTop(); doScrollSync();
$previewElt.find(".wmd-preview-section").each(function() { }, 500);
if(htmlSectionOffset === undefined) {
// Force start to 0 for the first section
htmlSectionOffset = 0;
return;
}
var $delimiterElt = $(this);
// Consider div scroll position
var newSectionOffset = $delimiterElt.position().top + previewScrollTop;
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: newSectionOffset,
height: newSectionOffset - htmlSectionOffset
});
htmlSectionOffset = newSectionOffset;
});
// Last section
scrollHeight = $previewElt.prop('scrollHeight');
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - htmlSectionOffset
});
// apply Scroll Link (-10 to have a gap > 9px) var isPreviewVisible = true;
lastEditorScrollTop = -10; var isScrollEditor = false;
lastPreviewScrollTop = -10; var isScrollPreview = false;
doScrollSync(); var isEditorMoving = false;
}, 500); var isPreviewMoving = false;
var isPreviewVisible = true; function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) {
var isScrollEditor = false; // Find the section corresponding to the offset
var isScrollPreview = false; var sectionIndex;
var isEditorMoving = false; var srcSection = _.find(srcSectionList, function(section, index) {
var isPreviewMoving = false; sectionIndex = index;
function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) { return srcScrollTop < section.endOffset;
// Find the section corresponding to the offset });
var sectionIndex; if(srcSection === undefined) {
var srcSection = _.find(srcSectionList, function(section, index) { // Something very bad happened
sectionIndex = index; return;
return srcScrollTop < section.endOffset; }
}); var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
if(srcSection === undefined) { var destSection = destSectionList[sectionIndex];
// Something very bad happened return destSection.startOffset + destSection.height * posInSection;
return; }
}
var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
var destSection = destSectionList[sectionIndex];
return destSection.startOffset + destSection.height * posInSection;
}
var timeoutId; var timeoutId;
var currentEndCb; var currentEndCb;
function animate(elt, startValue, endValue, stepCb, endCb) {
if(currentEndCb) {
clearTimeout(timeoutId);
currentEndCb();
}
currentEndCb = endCb;
var diff = endValue - startValue;
var startTime = Date.now();
function tick() {
var currentTime = Date.now();
var progress = (currentTime - startTime) / 200;
if(progress < 1) {
var scrollTop = startValue + diff * Math.cos((1 - progress) * Math.PI / 2 );
elt.scrollTop = scrollTop;
stepCb(scrollTop);
timeoutId = setTimeout(tick, 1);
}
else {
currentEndCb = undefined;
elt.scrollTop = endValue;
endCb();
}
}
tick();
}
var doScrollSync = _.throttle(function() { function animate(elt, startValue, endValue, stepCb, endCb) {
if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { if(currentEndCb) {
return; clearTimeout(timeoutId);
} currentEndCb();
var editorScrollTop = $editorElt.scrollTop(); }
editorScrollTop < 0 && (editorScrollTop = 0); currentEndCb = endCb;
var previewScrollTop = $previewElt.scrollTop(); var diff = endValue - startValue;
var destScrollTop; var startTime = Date.now();
// Perform the animation if diff > 9px
if(isScrollEditor === true) {
if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) {
return;
}
isScrollEditor = false;
// Animate the preview
lastEditorScrollTop = editorScrollTop;
destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList);
destScrollTop = _.min([
destScrollTop,
$previewElt.prop('scrollHeight') - $previewElt.outerHeight()
]);
if(Math.abs(destScrollTop - previewScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastPreviewScrollTop = previewScrollTop;
return;
}
animate($previewElt[0], previewScrollTop, destScrollTop, function(currentScrollTop) {
isPreviewMoving = true;
lastPreviewScrollTop = currentScrollTop;
}, function() {
isPreviewMoving = false;
});
}
else if(isScrollPreview === true) {
if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) {
return;
}
isScrollPreview = false;
// Animate the editor
lastPreviewScrollTop = previewScrollTop;
destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
destScrollTop = _.min([
destScrollTop,
$editorElt.prop('scrollHeight') - $editorElt.outerHeight()
]);
if(Math.abs(destScrollTop - editorScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastEditorScrollTop = editorScrollTop;
return;
}
animate($editorElt[0], editorScrollTop, destScrollTop, function(currentScrollTop) {
isEditorMoving = true;
lastEditorScrollTop = currentScrollTop;
}, function() {
isEditorMoving = false;
});
}
}, 100);
scrollSync.onLayoutResize = function() { function tick() {
isScrollEditor = true; var currentTime = Date.now();
buildSections(); var progress = (currentTime - startTime) / 200;
}; if(progress < 1) {
var scrollTop = startValue + diff * Math.cos((1 - progress) * Math.PI / 2);
elt.scrollTop = scrollTop;
stepCb(scrollTop);
timeoutId = setTimeout(tick, 1);
}
else {
currentEndCb = undefined;
elt.scrollTop = endValue;
endCb();
}
}
scrollSync.onFileClosed = function() { tick();
mdSectionList = []; }
};
var scrollAdjust = false; var doScrollSync = _.throttle(function() {
scrollSync.onReady = function() { if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
$previewElt = $(".preview-container"); return;
$editorElt = $("#wmd-input"); }
var editorScrollTop = editorElt.scrollTop;
editorScrollTop < 0 && (editorScrollTop = 0);
var previewScrollTop = previewElt.scrollTop;
var destScrollTop;
// Perform the animation if diff > 9px
if(isScrollEditor === true) {
if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) {
return;
}
isScrollEditor = false;
// Animate the preview
lastEditorScrollTop = editorScrollTop;
destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList);
destScrollTop = _.min([
destScrollTop,
previewElt.scrollHeight - previewElt.offsetHeight
]);
if(Math.abs(destScrollTop - previewScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastPreviewScrollTop = previewScrollTop;
return;
}
animate(previewElt, previewScrollTop, destScrollTop, function(currentScrollTop) {
isPreviewMoving = true;
lastPreviewScrollTop = currentScrollTop;
}, function() {
isPreviewMoving = false;
});
}
else if(isScrollPreview === true) {
if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) {
return;
}
isScrollPreview = false;
// Animate the editor
lastPreviewScrollTop = previewScrollTop;
destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
destScrollTop = _.min([
destScrollTop,
editorElt.scrollHeight - editorElt.offsetHeight
]);
if(Math.abs(destScrollTop - editorScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastEditorScrollTop = editorScrollTop;
return;
}
animate(editorElt, editorScrollTop, destScrollTop, function(currentScrollTop) {
isEditorMoving = true;
lastEditorScrollTop = currentScrollTop;
}, function() {
isEditorMoving = false;
});
}
}, 100);
$previewElt.scroll(function() { scrollSync.onLayoutResize = function() {
if(isPreviewMoving === false && scrollAdjust === false) { isScrollEditor = true;
isScrollPreview = true; buildSections();
isScrollEditor = false; };
doScrollSync();
}
scrollAdjust = false;
});
$editorElt.scroll(function() {
if(isEditorMoving === false) {
isScrollEditor = true;
isScrollPreview = false;
doScrollSync();
}
});
$(".preview-panel").on('hide.layout.toggle', function() { scrollSync.onFileClosed = function() {
isPreviewVisible = false; mdSectionList = [];
}).on('shown.layout.toggle', function() { };
isPreviewVisible = true;
});
// Reimplement anchor scrolling to work without preview var scrollAdjust = false;
$('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) { scrollSync.onReady = function() {
evt.preventDefault(); previewElt = document.querySelector(".preview-container");
var id = this.hash; editorElt = document.querySelector("#wmd-input");
var $anchorElt = $previewElt.find(id);
if(!$anchorElt.length) {
return;
}
var previewScrollTop = $anchorElt.offset().top - $previewElt.offset().top + $previewElt.scrollTop();
$previewElt.scrollTop(previewScrollTop);
var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
$editorElt.scrollTop(editorScrollTop);
});
};
var $previewContentsElt; $(previewElt).scroll(function() {
scrollSync.onPagedownConfigure = function(editor) { if(isPreviewMoving === false && scrollAdjust === false) {
$previewContentsElt = $("#preview-contents"); isScrollPreview = true;
editor.getConverter().hooks.chain("postConversion", function(text) { isScrollEditor = false;
// To avoid losing scrolling position before elements are fully loaded doScrollSync();
$previewContentsElt.height($previewContentsElt.height()); }
return text; scrollAdjust = false;
}); });
}; $(editorElt).scroll(function() {
if(isEditorMoving === false) {
isScrollEditor = true;
isScrollPreview = false;
doScrollSync();
}
});
scrollSync.onPreviewFinished = function() { $(".preview-panel").on('hide.layout.toggle', function() {
// Now set the correct height isPreviewVisible = false;
var previousHeight = $previewContentsElt.height(); }).on('shown.layout.toggle', function() {
$previewContentsElt.height("auto"); isPreviewVisible = true;
var newHeight = $previewContentsElt.height(); });
isScrollEditor = true;
if(newHeight < previousHeight) {
// We expect a scroll adjustment
scrollAdjust = true;
}
buildSections();
};
return scrollSync; // Reimplement anchor scrolling to work without preview
$('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
evt.preventDefault();
var id = this.hash;
var anchorElt = previewElt.querySelector(id);
if(!anchorElt) {
return;
}
var previewScrollTop = anchorElt.getBoundingClientRect().top - previewElt.getBoundingClientRect().top + previewElt.scrollTop;
previewElt.scrollTop = previewScrollTop;
var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
editorElt.scrollTop = editorScrollTop;
});
};
var previewContentsElt;
var previousHeight;
scrollSync.onPagedownConfigure = function(editor) {
previewContentsElt = document.getElementById("preview-contents");
editor.getConverter().hooks.chain("postConversion", function(text) {
// To avoid losing scrolling position before elements are fully loaded
previousHeight = previewContentsElt.offsetHeight;
previewContentsElt.style.height = previousHeight + 'px';
return text;
});
};
scrollSync.onPreviewFinished = function() {
// Now set the correct height
previewContentsElt.style.removeProperty('height');
var newHeight = previewContentsElt.offsetHeight;
isScrollEditor = true;
if(newHeight < previousHeight) {
// We expect a scroll adjustment
scrollAdjust = true;
}
buildSections();
};
return scrollSync;
}); });