3535<script >
3636import {codemirror } from ' vue-codemirror-lite'
3737import ' codemirror/mode/markdown/markdown.js'
38+ import _ from ' lodash'
3839
3940export default {
4041 name: ' input-area' ,
@@ -47,6 +48,9 @@ export default {
4748 },
4849 insertCode: {
4950 default: null
51+ },
52+ scrollSync: {
53+ type: Boolean
5054 }
5155 },
5256 computed: {
@@ -83,19 +87,104 @@ export default {
8387 },
8488 data : function () {
8589 return {
86- code: ' '
90+ code: ' ' ,
91+ scrollSynced: false ,
92+ scrollAnimation: null
8793 }
8894 },
8995 components: {
9096 codemirror
9197 },
9298 mounted : function () {
9399 this .code = this .value
100+
101+ const debouncedEmitScrollSync = _ .debounce (this .emitScrollSync , 50 , { maxWait: 50 })
102+ const scrollSync = () => {
103+ if (this .scrollSynced ) {
104+ this .scrollSynced = false
105+ } else {
106+ debouncedEmitScrollSync ()
107+ if (this .scrollAnimation ) {
108+ this .scrollAnimation .cancel ()
109+ }
110+ }
111+ }
112+ this .editor .on (' cursorActivity' , scrollSync)
113+ this .editor .on (' scroll' , scrollSync)
94114 },
95115 methods: {
96116 updateCode (code ) {
97117 this .$emit (' input' , code)
118+ },
119+
120+ emitScrollSync () {
121+ if (! this .scrollSync ) return
122+ const cursorLine = this .editor .getCursor ().line
123+ const scrollInfo = this .editor .getScrollInfo (' local' )
124+ const viewport = {
125+ from: this .editor .lineAtHeight (scrollInfo .top , ' local' ),
126+ to: this .editor .lineAtHeight (scrollInfo .top + scrollInfo .clientHeight , ' local' ) + 1
127+ }
128+ const linesOffset = []
129+ for (let line = viewport .from ; line < viewport .to ; ++ line) {
130+ const coords = this .editor .cursorCoords ({ line, ch: 0 }, ' local' )
131+ linesOffset[line] = {
132+ top: coords .top - scrollInfo .top ,
133+ bottom: coords .bottom - scrollInfo .top
134+ }
135+ }
136+ const event = {
137+ cursorLine, scrollInfo, viewport, linesOffset
138+ }
139+ this .$emit (' scroll-sync' , event )
140+ },
141+ updateScrollSync ({ scrollInfo, linesOffset }) {
142+ if (! linesOffset .length ) {
143+ return
144+ }
145+ const scrollMid = scrollInfo .height / 2
146+ let syncLine
147+ linesOffset .forEach (({ top }, line ) => {
148+ if (typeof syncLine === ' undefined' || Math .abs (top - scrollMid) < Math .abs (linesOffset[syncLine].top - scrollMid)) {
149+ syncLine = line
150+ }
151+ })
152+ const scrollTop = this .editor .getScrollInfo ().top
153+ const editorLineOffset = this .editor .heightAtLine (syncLine, ' local' ) - scrollTop
154+ if (this .scrollAnimation ) {
155+ this .scrollAnimation .cancel ()
156+ }
157+ let animationCancelled = false
158+ let animationSkipFrame = false
159+ const animationFrom = scrollTop
160+ const animationTo = scrollTop + editorLineOffset - linesOffset[syncLine].top
161+ const animationStartTime = Date .now ()
162+ const animationDuration = 200
163+ const animationFrameCallback = () => {
164+ if (animationSkipFrame) {
165+ // skip frame so that user can scroll to interrupt animation
166+ requestAnimationFrame (animationFrameCallback)
167+ animationSkipFrame = false
168+ } else if (! animationCancelled) {
169+ const currentTime = Date .now ()
170+ const precent = (currentTime - animationStartTime) / animationDuration
171+ this .scrollSynced = true
172+ if (precent >= 1 ) {
173+ this .editor .scrollTo (null , animationTo)
174+ } else {
175+ this .editor .scrollTo (null , (animationTo - animationFrom) * precent + animationFrom)
176+ requestAnimationFrame (animationFrameCallback)
177+ animationSkipFrame = true
178+ }
179+ }
180+ }
181+ animationFrameCallback ()
182+ this .scrollAnimation = {
183+ cancel () {
184+ animationCancelled = true
185+ }
186+ }
98187 }
99188 }
100189}
101- </script >
190+ </script >
0 commit comments