Skip to content

Commit b28750f

Browse files
committed
Update to improve syncscroll performance and add toggle for sync scrolling
1 parent bf1dc23 commit b28750f

3 files changed

Lines changed: 128 additions & 132 deletions

File tree

public/css/index.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ body {
112112
background-color: white;
113113
box-shadow: 5px 0px 10px #e7e7e7;
114114
}
115+
.ui-edit-area .ui-sync-toggle {
116+
width: 42px;
117+
height: 42px;
118+
padding: 3px 1px 0 0;
119+
border-radius: 50%;
120+
border-color: #e7e7e7;
121+
position: absolute;
122+
top: 50%;
123+
left: 50%;
124+
transform: translate(-50%, -50%);
125+
}
115126
.ui-view-area {
116127
/*overflow-y: scroll;*/
117128
-webkit-overflow-scrolling: touch;

public/js/index.js

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,11 @@ var ui = {
552552
codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
553553
codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
554554
codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
555-
markdown: $(".ui-view-area .markdown-body")
555+
markdown: $(".ui-view-area .markdown-body"),
556+
resize: {
557+
handle: $('.ui-resizable-handle'),
558+
syncToggle: $('.ui-sync-toggle')
559+
}
556560
},
557561
modal: {
558562
snippetImportProjects: $("#snippetImportModalProjects"),
@@ -705,30 +709,6 @@ $(window).error(function () {
705709
//setNeedRefresh();
706710
});
707711

708-
//when page hash change
709-
window.onhashchange = locationHashChanged;
710-
711-
function locationHashChanged(e) {
712-
e.stopPropagation();
713-
e.preventDefault();
714-
if (currentMode != modeType.both) {
715-
return;
716-
}
717-
var hashtarget = $("[id$='" + location.hash.substr(1) + "']");
718-
if (hashtarget.length > 0) {
719-
var linenumber = hashtarget.attr('data-startline');
720-
if (linenumber) {
721-
editor.setOption('viewportMargin', Infinity);
722-
editor.setOption('viewportMargin', viewportMargin);
723-
var t = editor.charCoords({
724-
line: linenumber,
725-
ch: 0
726-
}, "local").top;
727-
editor.scrollTo(null, t - defaultTextHeight * 1.2);
728-
}
729-
}
730-
}
731-
732712
var windowResizeDebounce = 200;
733713
var windowResize = _.debounce(windowResizeInner, windowResizeDebounce);
734714

@@ -814,10 +794,38 @@ function checkEditorStyle() {
814794
handles: 'e',
815795
maxWidth: $(window).width() * 0.7,
816796
minWidth: $(window).width() * 0.2,
797+
resize: function (e) {
798+
ui.area.resize.syncToggle.stop(true, true).show();
799+
},
817800
stop: function (e) {
818801
lastEditorWidth = ui.area.edit.width();
819802
}
820803
});
804+
if (!ui.area.resize.handle.length) {
805+
ui.area.resize.handle = $('.ui-resizable-handle');
806+
}
807+
if (!ui.area.resize.syncToggle.length) {
808+
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>');
809+
ui.area.resize.syncToggle.click(function () {
810+
syncscroll = !syncscroll;
811+
checkSyncToggle();
812+
});
813+
ui.area.resize.handle.append(ui.area.resize.syncToggle);
814+
ui.area.resize.syncToggle.hide();
815+
ui.area.resize.handle.hover(function () {
816+
ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100);
817+
}, function () {
818+
ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300);
819+
});
820+
}
821+
}
822+
823+
function checkSyncToggle() {
824+
if (syncscroll) {
825+
ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link');
826+
} else {
827+
ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
828+
}
821829
}
822830

823831
function checkEditorScrollbar() {
@@ -984,17 +992,18 @@ function changeMode(type) {
984992
ui.area.edit.css('width', lastEditorWidth + 'px');
985993
else
986994
ui.area.edit.css('width', '');
987-
ui.area.edit.find('.ui-resizable-handle').show();
995+
ui.area.resize.handle.show();
988996
} else {
989997
ui.area.edit.css('width', '');
990-
ui.area.edit.find('.ui-resizable-handle').hide();
998+
ui.area.resize.handle.hide();
991999
}
9921000

9931001
windowResizeInner();
9941002

9951003
restoreInfo();
9961004

9971005
if (lastMode == modeType.view && currentMode == modeType.both) {
1006+
preventSyncScrollToView = true;
9981007
syncScrollToEdit();
9991008
}
10001009

public/js/syncscroll.js

Lines changed: 81 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,13 @@ md.use(window.markdownitContainer, 'info', { render: renderContainer });
105105
md.use(window.markdownitContainer, 'warning', { render: renderContainer });
106106
md.use(window.markdownitContainer, 'danger', { render: renderContainer });
107107

108-
var preventSyncScroll = false;
108+
var syncscroll = true;
109+
110+
var preventSyncScrollToEdit = false;
111+
var preventSyncScrollToView = false;
109112

110113
var editScrollThrottle = 1;
111-
var viewScrollThrottle = 20;
114+
var viewScrollThrottle = 10;
112115
var buildMapThrottle = 100;
113116

114117
var viewScrolling = false;
@@ -126,71 +129,6 @@ if (editor.getOption('scrollbarStyle') === 'native') {
126129
}
127130
ui.area.view.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle));
128131

129-
var preventViewScroll = false;
130-
131-
function syncScrollToEdit(e) {
132-
if (currentMode != modeType.both) return;
133-
if (preventViewScroll) {
134-
if (typeof preventViewScroll === 'number') {
135-
preventViewScroll--;
136-
} else {
137-
preventViewScroll = false;
138-
}
139-
return;
140-
}
141-
if (!scrollMap || !lineHeightMap) {
142-
buildMap(true);
143-
return;
144-
}
145-
if (editScrolling) return;
146-
var scrollTop = ui.area.view[0].scrollTop;
147-
var lineIndex = 0;
148-
for (var i = 0, l = scrollMap.length; i < l; i++) {
149-
if (scrollMap[i] > scrollTop) {
150-
break;
151-
} else {
152-
lineIndex = i;
153-
}
154-
}
155-
var lineNo = 0;
156-
var lineDiff = 0;
157-
for (var i = 0, l = lineHeightMap.length; i < l; i++) {
158-
if (lineHeightMap[i] > lineIndex) {
159-
break;
160-
} else {
161-
lineNo = lineHeightMap[i];
162-
lineDiff = lineHeightMap[i + 1] - lineNo;
163-
}
164-
}
165-
166-
var scrollInfo = editor.getScrollInfo();
167-
var textHeight = editor.defaultTextHeight();
168-
var posTo = 0;
169-
var topDiffPercent = 0;
170-
var posToNextDiff = 0;
171-
var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
172-
var preLastLineNo = Math.round(preLastLineHeight / textHeight);
173-
174-
if (scrollInfo.height > scrollInfo.clientHeight && lineNo >= preLastLineNo) {
175-
posTo = preLastLineHeight;
176-
topDiffPercent = (scrollTop - scrollMap[preLastLineNo]) / (viewBottom - scrollMap[preLastLineNo]);
177-
posToNextDiff = Math.ceil(textHeight * topDiffPercent);
178-
} else {
179-
posTo = lineNo * textHeight;
180-
topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
181-
posToNextDiff = Math.ceil(textHeight * lineDiff * topDiffPercent);
182-
}
183-
184-
editor.scrollTo(0, posTo + posToNextDiff);
185-
preventSyncScroll = true;
186-
187-
viewScrolling = true;
188-
clearTimeout(viewScrollingTimer);
189-
viewScrollingTimer = setTimeout(function () {
190-
viewScrolling = false;
191-
}, viewScrollingDelay);
192-
}
193-
194132
var scrollMap, lineHeightMap, viewTop, viewBottom;
195133

196134
viewAjaxCallback = clearMap;
@@ -279,60 +217,98 @@ function buildMapInner(syncBack) {
279217
scrollMap = _scrollMap;
280218
lineHeightMap = _lineHeightMap;
281219

282-
if (loaded && syncBack)
220+
if (loaded && syncBack) {
283221
syncScrollToView();
222+
syncScrollToEdit();
223+
}
284224
}
285225

286-
function getPartByEditorLineNo(lineNo) {
287-
var part = null;
288-
ui.area.markdown.find('.part').each(function (n, el) {
289-
if (part) return;
290-
var $el = $(el),
291-
t = $el.data('startline') - 1,
292-
f = $el.data('endline') - 1;
293-
if (t === '' || f === '') {
294-
return;
226+
227+
function syncScrollToEdit(e) {
228+
if (currentMode != modeType.both || !syncscroll) return;
229+
if (preventSyncScrollToEdit) {
230+
if (typeof preventSyncScrollToEdit === 'number') {
231+
preventSyncScrollToEdit--;
232+
} else {
233+
preventSyncScrollToEdit = false;
295234
}
296-
if (lineNo >= t && lineNo <= f) {
297-
part = $el;
235+
return;
236+
}
237+
if (!scrollMap || !lineHeightMap) {
238+
buildMap(true);
239+
return;
240+
}
241+
if (editScrolling) return;
242+
243+
var scrollTop = ui.area.view[0].scrollTop;
244+
var lineIndex = 0;
245+
for (var i = 0, l = scrollMap.length; i < l; i++) {
246+
if (scrollMap[i] > scrollTop) {
247+
break;
248+
} else {
249+
lineIndex = i;
298250
}
299-
});
300-
if (part)
301-
return {
302-
startline: part.data('startline') - 1,
303-
endline: part.data('endline') - 1,
304-
linediff: Math.abs(part.data('endline') - part.data('startline')) + 1,
305-
element: part
306-
};
307-
else
308-
return null;
309-
}
310-
311-
function getEditorLineNoByTop(top) {
312-
for (var i = 0; i < lineHeightMap.length; i++)
313-
if (lineHeightMap[i] * editor.defaultTextHeight() > top)
314-
return i;
315-
return null;
251+
}
252+
var lineNo = 0;
253+
var lineDiff = 0;
254+
for (var i = 0, l = lineHeightMap.length; i < l; i++) {
255+
if (lineHeightMap[i] > lineIndex) {
256+
break;
257+
} else {
258+
lineNo = lineHeightMap[i];
259+
lineDiff = lineHeightMap[i + 1] - lineNo;
260+
}
261+
}
262+
263+
var posTo = 0;
264+
var topDiffPercent = 0;
265+
var posToNextDiff = 0;
266+
var scrollInfo = editor.getScrollInfo();
267+
var textHeight = editor.defaultTextHeight();
268+
var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
269+
var preLastLineNo = Math.round(preLastLineHeight / textHeight);
270+
var preLastLinePos = scrollMap[preLastLineNo];
271+
272+
if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
273+
posTo = preLastLineHeight;
274+
topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos);
275+
posToNextDiff = Math.ceil(textHeight * topDiffPercent);
276+
} else {
277+
posTo = lineNo * textHeight;
278+
topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
279+
posToNextDiff = Math.ceil(textHeight * lineDiff * topDiffPercent);
280+
}
281+
282+
editor.scrollTo(0, posTo + posToNextDiff);
283+
preventSyncScrollToView = true;
284+
285+
viewScrolling = true;
286+
clearTimeout(viewScrollingTimer);
287+
viewScrollingTimer = setTimeout(function () {
288+
viewScrolling = false;
289+
}, viewScrollingDelay);
316290
}
317291

318292
function syncScrollToView(event, _lineNo) {
319-
if (currentMode != modeType.both) return;
320-
if (preventSyncScroll) {
321-
if (typeof preventSyncScroll === 'number') {
322-
preventSyncScroll--;
293+
if (currentMode != modeType.both || !syncscroll) return;
294+
if (preventSyncScrollToView) {
295+
if (typeof preventSyncScrollToView === 'number') {
296+
preventSyncScrollToView--;
323297
} else {
324-
preventSyncScroll = false;
298+
preventSyncScrollToView = false;
325299
}
326300
return;
327301
}
328-
var lineNo, posTo;
329-
var scrollInfo = editor.getScrollInfo();
330302
if (!scrollMap || !lineHeightMap) {
331303
buildMap(true);
332304
return;
333305
}
306+
if (viewScrolling) return;
307+
334308
if (!_lineNo) {
309+
var lineNo, posTo;
335310
var topDiffPercent, posToNextDiff;
311+
var scrollInfo = editor.getScrollInfo();
336312
var textHeight = editor.defaultTextHeight();
337313
lineNo = Math.floor(scrollInfo.top / textHeight);
338314
// if reach the last line, will start lerp to the bottom
@@ -349,11 +325,11 @@ function syncScrollToView(event, _lineNo) {
349325
posTo += Math.floor(posToNextDiff);
350326
}
351327
} else {
352-
if (viewScrolling) return;
353328
posTo = scrollMap[lineHeightMap[_lineNo]];
354329
}
330+
355331
ui.area.view.stop(true, true).scrollTop(posTo);
356-
preventViewScroll = true;
332+
preventSyncScrollToEdit = true;
357333

358334
editScrolling = true;
359335
clearTimeout(editScrollingTimer);

0 commit comments

Comments
 (0)