2019.1.22 簡書官方已經(jīng)加入了同步滾動的功能北发。
但是官方實(shí)現(xiàn)的時(shí)候员串,優(yōu)先的是頂端對齊,也就是編輯區(qū)和預(yù)覽區(qū)在頂部一定是同步的壕曼。本文的實(shí)現(xiàn)是優(yōu)先底部對齊苏研,也就是如果滑動到最后,預(yù)覽和編輯是一定會對齊的腮郊。我覺得優(yōu)先底部對齊更實(shí)用摹蘑,因?yàn)閷懽鞯倪^程中在文末添加內(nèi)容的場景更多,這個(gè)時(shí)候需要對齊轧飞。
如你所見衅鹿,右側(cè)的預(yù)覽并不能實(shí)時(shí)地與左側(cè)的輸入進(jìn)行同步。
這是很氣的过咬。
哎呦這是最氣的大渤。
所以我寫了一個(gè)腳本,點(diǎn)擊此處安裝(需要先行安裝瀏覽器用戶腳本擴(kuò)展)掸绞。
經(jīng)過評論區(qū)小伙伴的反饋泵三,這里還真遇到一個(gè)坑:
一開始的版本做的比較簡單,當(dāng)輸入框接收到滾動事件后,就直接向預(yù)覽框也發(fā)送這個(gè)滾動事件烫幕。
但是當(dāng)瀏覽器開啟平滑滾動時(shí)俺抽,一次鼠標(biāo)滾輪的滾動會被瀏覽器分解成多段來處理,以達(dá)到平滑的效果较曼。
平滑滾動的情況下磷斧,被分解的滾動事件的頭一段的像素移動非常小,我們的腳本卻會把這個(gè)小滾動事件也照舊作用在預(yù)覽框上捷犹。問題來了弛饭,此時(shí)預(yù)覽框接收到滾動事件后,又通過我們這個(gè)腳本向輸入框上發(fā)了一個(gè)小滾動事件伏恐,相當(dāng)于“反彈”了一下孩哑。也就是說,如果沒有這個(gè)“反彈”回來的滾動事件影響翠桦,預(yù)覽框本應(yīng)該繼續(xù)進(jìn)行接下來的平滑滾動分解動作横蜒,但是卻收到了來自我們腳本的小滾動事件,就忽視了接下來的平滑滾動分解動作销凑,所以每次滾動只滾動了很小的距離丛晌,看起來像是被卡住了。
所以問題在于考慮的太簡單斗幼,事件發(fā)生了來回相互觸發(fā)澎蛛。我們的腳本無法區(qū)別哪些是人產(chǎn)生的滾動事件,哪些是腳本發(fā)出的滾動事件蜕窿,導(dǎo)致事件來回反彈谋逻,影響正常工作。
解決辦法是手動的截?cái)嘤晌覀兊哪_本所產(chǎn)生的滾動事件桐经。步驟如下:
假如發(fā)生了編輯框的滾動事件計(jì) mainFlag
為 true毁兆,發(fā)生了預(yù)覽框的滾動事件計(jì) preFlag
為 true。
- 用戶滾動編輯框阴挣,觸發(fā)滾動事件气堕,
mainFlag
計(jì)為true
,觸發(fā)腳本改變預(yù)覽框位置畔咧。 - 上一步驟預(yù)覽框位置改變又觸發(fā)了預(yù)覽框滾動事件茎芭,
preFlag
計(jì)為true
,此時(shí)檢測發(fā)現(xiàn)mainFlag
也為true
誓沸,證明這次預(yù)覽框滾動事件是由腳本產(chǎn)生的事件梅桩,應(yīng)該被截?cái)唷#ㄈ绻唤財(cái)喟菟恚@次滾動事件又會觸發(fā)更改編輯框的動作宿百,引起 bug煮寡。) - 把兩個(gè)標(biāo)志位都恢復(fù)為
false
。
形象的說就是犀呼,由兩個(gè)標(biāo)志位來做“配對”,“配對”抵消下一個(gè)事件薇组。
1.3
版代碼如下:
// ==UserScript==
// @name 簡書 Markdown 預(yù)覽同步滾動
// @name:en Jianshu MD AUTO Scroll
// @namespace https://github.com/BlindingDark/JianshuMDAutoScroll
// @include *://www.reibang.com/writer*
// @version 1.3
// @description:en jianshu Markdown preview AUTO scroll
// @description 給簡書的在線 Markdown 編輯器增加輸入預(yù)覽同步滾動的功能
// @author BlindingDark
// @grant none
// @require https://cdn.bootcss.com/jquery/3.2.1/jquery.js
// ==/UserScript==
(function() {
'use strict';
var spSwitchMain; // 切換的那個(gè)按鈕所在的窗體
var txtMain; // 輸入框
var spPreview; // 預(yù)覽框
const SWITCH_FEATURE = 'a.fa.fa-columns';
const EXPAND_FEATURE = 'a.fa.fa-expand';
const COMPRESS_FEATURE = 'a.fa.fa-compress';
function getInput() {
return $('#arthur-editor');
}
function getPreview() {
return getInput().closest("div").parent().next();
}
function scrollEvent(){
txtMain = getInput()[0];
spPreview = getPreview()[0];
if(txtMain == undefined) {
return;
}
if(spPreview == undefined) {
return;
}
let mainFlag = false; // 抵消兩個(gè)滾動事件之間互相觸發(fā)
let preFlag = false; // 如果兩個(gè) flag 都為 true外臂,證明是反彈過來的事件引起的
function scrolling(who){
if(who == 'pre'){
preFlag = true;
if (mainFlag === true){ // 抵消兩個(gè)滾動事件之間互相觸發(fā)
mainFlag = false;
preFlag = false;
return;
}
txtMain.scrollTop = Math.round((spPreview.scrollTop + spPreview.clientHeight) * txtMain.scrollHeight / spPreview.scrollHeight - txtMain.clientHeight);
return;
}
if(who == 'main'){
mainFlag = true;
if (preFlag === true){ // 抵消兩個(gè)滾動事件之間互相觸發(fā)
mainFlag = false;
preFlag = false;
return;
}
spPreview.scrollTop = Math.round((txtMain.scrollTop + txtMain.clientHeight) * spPreview.scrollHeight / txtMain.scrollHeight - spPreview.clientHeight);
return;
}
}
function mainOnscroll(){
scrolling('main');
}
function preOnscroll(){
scrolling('pre');
}
getInput().on('scroll', () => mainOnscroll());
getPreview().on('scroll', () => preOnscroll());
}
function cycle() {
scrollEvent();
$(EXPAND_FEATURE).on('click', scrollEvent);
$(COMPRESS_FEATURE).on('click', scrollEvent);
$(SWITCH_FEATURE).on("click", scrollEvent);
window.setTimeout(cycle, 1000);
}
cycle();
})();
有興趣的同學(xué)可以直接前往 Github 改進(jìn)此腳本。