Update to improve syncscroll performance and add toggle for sync scrolling

This commit is contained in:
Cheng-Han, Wu 2016-05-26 13:17:00 +08:00
parent bf1dc237a9
commit b28750f256
3 changed files with 132 additions and 136 deletions

View file

@ -112,6 +112,17 @@ body {
background-color: white; background-color: white;
box-shadow: 5px 0px 10px #e7e7e7; box-shadow: 5px 0px 10px #e7e7e7;
} }
.ui-edit-area .ui-sync-toggle {
width: 42px;
height: 42px;
padding: 3px 1px 0 0;
border-radius: 50%;
border-color: #e7e7e7;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ui-view-area { .ui-view-area {
/*overflow-y: scroll;*/ /*overflow-y: scroll;*/
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;

View file

@ -552,7 +552,11 @@ var ui = {
codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"), codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"), codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"), codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
markdown: $(".ui-view-area .markdown-body") markdown: $(".ui-view-area .markdown-body"),
resize: {
handle: $('.ui-resizable-handle'),
syncToggle: $('.ui-sync-toggle')
}
}, },
modal: { modal: {
snippetImportProjects: $("#snippetImportModalProjects"), snippetImportProjects: $("#snippetImportModalProjects"),
@ -705,30 +709,6 @@ $(window).error(function () {
//setNeedRefresh(); //setNeedRefresh();
}); });
//when page hash change
window.onhashchange = locationHashChanged;
function locationHashChanged(e) {
e.stopPropagation();
e.preventDefault();
if (currentMode != modeType.both) {
return;
}
var hashtarget = $("[id$='" + location.hash.substr(1) + "']");
if (hashtarget.length > 0) {
var linenumber = hashtarget.attr('data-startline');
if (linenumber) {
editor.setOption('viewportMargin', Infinity);
editor.setOption('viewportMargin', viewportMargin);
var t = editor.charCoords({
line: linenumber,
ch: 0
}, "local").top;
editor.scrollTo(null, t - defaultTextHeight * 1.2);
}
}
}
var windowResizeDebounce = 200; var windowResizeDebounce = 200;
var windowResize = _.debounce(windowResizeInner, windowResizeDebounce); var windowResize = _.debounce(windowResizeInner, windowResizeDebounce);
@ -814,10 +794,38 @@ function checkEditorStyle() {
handles: 'e', handles: 'e',
maxWidth: $(window).width() * 0.7, maxWidth: $(window).width() * 0.7,
minWidth: $(window).width() * 0.2, minWidth: $(window).width() * 0.2,
resize: function (e) {
ui.area.resize.syncToggle.stop(true, true).show();
},
stop: function (e) { stop: function (e) {
lastEditorWidth = ui.area.edit.width(); lastEditorWidth = ui.area.edit.width();
} }
}); });
if (!ui.area.resize.handle.length) {
ui.area.resize.handle = $('.ui-resizable-handle');
}
if (!ui.area.resize.syncToggle.length) {
ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>');
ui.area.resize.syncToggle.click(function () {
syncscroll = !syncscroll;
checkSyncToggle();
});
ui.area.resize.handle.append(ui.area.resize.syncToggle);
ui.area.resize.syncToggle.hide();
ui.area.resize.handle.hover(function () {
ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100);
}, function () {
ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300);
});
}
}
function checkSyncToggle() {
if (syncscroll) {
ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link');
} else {
ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
}
} }
function checkEditorScrollbar() { function checkEditorScrollbar() {
@ -984,10 +992,10 @@ function changeMode(type) {
ui.area.edit.css('width', lastEditorWidth + 'px'); ui.area.edit.css('width', lastEditorWidth + 'px');
else else
ui.area.edit.css('width', ''); ui.area.edit.css('width', '');
ui.area.edit.find('.ui-resizable-handle').show(); ui.area.resize.handle.show();
} else { } else {
ui.area.edit.css('width', ''); ui.area.edit.css('width', '');
ui.area.edit.find('.ui-resizable-handle').hide(); ui.area.resize.handle.hide();
} }
windowResizeInner(); windowResizeInner();
@ -995,6 +1003,7 @@ function changeMode(type) {
restoreInfo(); restoreInfo();
if (lastMode == modeType.view && currentMode == modeType.both) { if (lastMode == modeType.view && currentMode == modeType.both) {
preventSyncScrollToView = true;
syncScrollToEdit(); syncScrollToEdit();
} }

View file

@ -105,10 +105,13 @@ md.use(window.markdownitContainer, 'info', { render: renderContainer });
md.use(window.markdownitContainer, 'warning', { render: renderContainer }); md.use(window.markdownitContainer, 'warning', { render: renderContainer });
md.use(window.markdownitContainer, 'danger', { render: renderContainer }); md.use(window.markdownitContainer, 'danger', { render: renderContainer });
var preventSyncScroll = false; var syncscroll = true;
var preventSyncScrollToEdit = false;
var preventSyncScrollToView = false;
var editScrollThrottle = 1; var editScrollThrottle = 1;
var viewScrollThrottle = 20; var viewScrollThrottle = 10;
var buildMapThrottle = 100; var buildMapThrottle = 100;
var viewScrolling = false; var viewScrolling = false;
@ -126,71 +129,6 @@ if (editor.getOption('scrollbarStyle') === 'native') {
} }
ui.area.view.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)); ui.area.view.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle));
var preventViewScroll = false;
function syncScrollToEdit(e) {
if (currentMode != modeType.both) return;
if (preventViewScroll) {
if (typeof preventViewScroll === 'number') {
preventViewScroll--;
} else {
preventViewScroll = false;
}
return;
}
if (!scrollMap || !lineHeightMap) {
buildMap(true);
return;
}
if (editScrolling) return;
var scrollTop = ui.area.view[0].scrollTop;
var lineIndex = 0;
for (var i = 0, l = scrollMap.length; i < l; i++) {
if (scrollMap[i] > scrollTop) {
break;
} else {
lineIndex = i;
}
}
var lineNo = 0;
var lineDiff = 0;
for (var i = 0, l = lineHeightMap.length; i < l; i++) {
if (lineHeightMap[i] > lineIndex) {
break;
} else {
lineNo = lineHeightMap[i];
lineDiff = lineHeightMap[i + 1] - lineNo;
}
}
var scrollInfo = editor.getScrollInfo();
var textHeight = editor.defaultTextHeight();
var posTo = 0;
var topDiffPercent = 0;
var posToNextDiff = 0;
var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
var preLastLineNo = Math.round(preLastLineHeight / textHeight);
if (scrollInfo.height > scrollInfo.clientHeight && lineNo >= preLastLineNo) {
posTo = preLastLineHeight;
topDiffPercent = (scrollTop - scrollMap[preLastLineNo]) / (viewBottom - scrollMap[preLastLineNo]);
posToNextDiff = Math.ceil(textHeight * topDiffPercent);
} else {
posTo = lineNo * textHeight;
topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
posToNextDiff = Math.ceil(textHeight * lineDiff * topDiffPercent);
}
editor.scrollTo(0, posTo + posToNextDiff);
preventSyncScroll = true;
viewScrolling = true;
clearTimeout(viewScrollingTimer);
viewScrollingTimer = setTimeout(function () {
viewScrolling = false;
}, viewScrollingDelay);
}
var scrollMap, lineHeightMap, viewTop, viewBottom; var scrollMap, lineHeightMap, viewTop, viewBottom;
viewAjaxCallback = clearMap; viewAjaxCallback = clearMap;
@ -279,60 +217,98 @@ function buildMapInner(syncBack) {
scrollMap = _scrollMap; scrollMap = _scrollMap;
lineHeightMap = _lineHeightMap; lineHeightMap = _lineHeightMap;
if (loaded && syncBack) if (loaded && syncBack) {
syncScrollToView(); syncScrollToView();
syncScrollToEdit();
}
} }
function getPartByEditorLineNo(lineNo) {
var part = null;
ui.area.markdown.find('.part').each(function (n, el) {
if (part) return;
var $el = $(el),
t = $el.data('startline') - 1,
f = $el.data('endline') - 1;
if (t === '' || f === '') {
return;
}
if (lineNo >= t && lineNo <= f) {
part = $el;
}
});
if (part)
return {
startline: part.data('startline') - 1,
endline: part.data('endline') - 1,
linediff: Math.abs(part.data('endline') - part.data('startline')) + 1,
element: part
};
else
return null;
}
function getEditorLineNoByTop(top) { function syncScrollToEdit(e) {
for (var i = 0; i < lineHeightMap.length; i++) if (currentMode != modeType.both || !syncscroll) return;
if (lineHeightMap[i] * editor.defaultTextHeight() > top) if (preventSyncScrollToEdit) {
return i; if (typeof preventSyncScrollToEdit === 'number') {
return null; preventSyncScrollToEdit--;
}
function syncScrollToView(event, _lineNo) {
if (currentMode != modeType.both) return;
if (preventSyncScroll) {
if (typeof preventSyncScroll === 'number') {
preventSyncScroll--;
} else { } else {
preventSyncScroll = false; preventSyncScrollToEdit = false;
} }
return; return;
} }
var lineNo, posTo;
var scrollInfo = editor.getScrollInfo();
if (!scrollMap || !lineHeightMap) { if (!scrollMap || !lineHeightMap) {
buildMap(true); buildMap(true);
return; return;
} }
if (editScrolling) return;
var scrollTop = ui.area.view[0].scrollTop;
var lineIndex = 0;
for (var i = 0, l = scrollMap.length; i < l; i++) {
if (scrollMap[i] > scrollTop) {
break;
} else {
lineIndex = i;
}
}
var lineNo = 0;
var lineDiff = 0;
for (var i = 0, l = lineHeightMap.length; i < l; i++) {
if (lineHeightMap[i] > lineIndex) {
break;
} else {
lineNo = lineHeightMap[i];
lineDiff = lineHeightMap[i + 1] - lineNo;
}
}
var posTo = 0;
var topDiffPercent = 0;
var posToNextDiff = 0;
var scrollInfo = editor.getScrollInfo();
var textHeight = editor.defaultTextHeight();
var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
var preLastLineNo = Math.round(preLastLineHeight / textHeight);
var preLastLinePos = scrollMap[preLastLineNo];
if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
posTo = preLastLineHeight;
topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos);
posToNextDiff = Math.ceil(textHeight * topDiffPercent);
} else {
posTo = lineNo * textHeight;
topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
posToNextDiff = Math.ceil(textHeight * lineDiff * topDiffPercent);
}
editor.scrollTo(0, posTo + posToNextDiff);
preventSyncScrollToView = true;
viewScrolling = true;
clearTimeout(viewScrollingTimer);
viewScrollingTimer = setTimeout(function () {
viewScrolling = false;
}, viewScrollingDelay);
}
function syncScrollToView(event, _lineNo) {
if (currentMode != modeType.both || !syncscroll) return;
if (preventSyncScrollToView) {
if (typeof preventSyncScrollToView === 'number') {
preventSyncScrollToView--;
} else {
preventSyncScrollToView = false;
}
return;
}
if (!scrollMap || !lineHeightMap) {
buildMap(true);
return;
}
if (viewScrolling) return;
if (!_lineNo) { if (!_lineNo) {
var lineNo, posTo;
var topDiffPercent, posToNextDiff; var topDiffPercent, posToNextDiff;
var scrollInfo = editor.getScrollInfo();
var textHeight = editor.defaultTextHeight(); var textHeight = editor.defaultTextHeight();
lineNo = Math.floor(scrollInfo.top / textHeight); lineNo = Math.floor(scrollInfo.top / textHeight);
// if reach the last line, will start lerp to the bottom // if reach the last line, will start lerp to the bottom
@ -349,11 +325,11 @@ function syncScrollToView(event, _lineNo) {
posTo += Math.floor(posToNextDiff); posTo += Math.floor(posToNextDiff);
} }
} else { } else {
if (viewScrolling) return;
posTo = scrollMap[lineHeightMap[_lineNo]]; posTo = scrollMap[lineHeightMap[_lineNo]];
} }
ui.area.view.stop(true, true).scrollTop(posTo); ui.area.view.stop(true, true).scrollTop(posTo);
preventViewScroll = true; preventSyncScrollToEdit = true;
editScrolling = true; editScrolling = true;
clearTimeout(editScrollingTimer); clearTimeout(editScrollingTimer);