minimap效果的實(shí)現(xiàn)
在項(xiàng)目中墨技,需要制作出縮略圖的效果斜姥。效果如下:
在firefox可以使用 element
屬性實(shí)現(xiàn)該效果蜈亩。(其它瀏覽器暫不支持)蒸眠。這里使用iframe來達(dá)到更好的兼容性。
效果分析
minimap主要是對主體內(nèi)容進(jìn)行映射询件。主體html如下:
<body>
<div class="content">
<p>
文章內(nèi)容
</p>
<img src="..">
</div>
</body>
我們需要將需要的html內(nèi)容燃乍,嵌入到iframe中。
# 這里獲取整個document下的html
let html = document.documentElement.outerHTML
# 將html內(nèi)容寫入到目標(biāo)iframe中
targetIframe.wirte(html)
整體邏輯是這樣宛琅。接著實(shí)現(xiàn)點(diǎn)擊橘沥、滾動、拖拽等事件夯秃。如果有大量交互行為的頁面座咆,需要對minimap做更多的優(yōu)化。
顯示效果的實(shí)現(xiàn)
codepen地址
考慮頁面加載的延遲效果仓洼,我們將minimap的js文件放在body的最后進(jìn)行引用(當(dāng)然介陶,建議動態(tài)頁面考慮其他的方式進(jìn)行實(shí)現(xiàn))。
minimap所處的div結(jié)構(gòu)應(yīng)該如下:
<div class="slider">
<iframe class="slider__content">
</div>
<div class="slider__size">
</div>
<div class="slider__controller">
</div>
</div>
iframe的作用就是映射主體的內(nèi)容的html色建。后面的 slider__size
slider__controller
有什么用哺呜?
我們對比一下有無后兩個div的差別:
前一個效果沒有選擇框,無法進(jìn)行滾動和拖拽箕戳。
實(shí)際上某残,slider__size
控制顯示區(qū)域的大小,slider__controller
顯示目前顯示的區(qū)域范圍陵吸。
計算顯示區(qū)域
minimap的顯示區(qū)域應(yīng)該達(dá)到以下效果
- iframe中內(nèi)容的縱橫比應(yīng)該和目標(biāo)區(qū)域的縱橫比相同
- iframe中內(nèi)容應(yīng)該進(jìn)行等比例縮放
那么玻墅, 我們假設(shè)目標(biāo)內(nèi)容的寬為px,高為
px壮虫,iframe的高為
px澳厢, 高度為
px环础。縮放比例為
那么就有:
js的實(shí)現(xiàn)如下
let bodyWidth = body.clientWidth,
# 整體內(nèi)容的長寬比
bodyRatio = docEl.clientHeight / docEl.clientWidth,
# window的長寬比
winRatio = win.innerHeight / win.innerWidth;
# 設(shè)置整個slider區(qū)域占父級區(qū)域的寬度
slider.style.width = '10%';
# 獲取scale的值
realScale = slider.clientWidth / bodyWidth;
# 進(jìn)行等比例縮放
sliderContent.style.transform = `scale(${realScale})`;
# 為content設(shè)置一個寬高剩拢。
sliderContent.style.width = (100 / realScale) + '%';
sliderContent.style.height = (100 / realScale) + '%';
這里有一個問題:
- 為什么
sliderContent
的值這樣設(shè)置线得?
slider__size是如何控制顯示區(qū)域的大小
首先,我們知道padding區(qū)域是一個透明的區(qū)域徐伐。我們可以通過控制padding區(qū)域的大小進(jìn)而就能控制需要顯示的區(qū)域贯钩。
-
將
slider__size
疊加到slider__content
上。這里只需要將
slider__content
定位屬性設(shè)置為absolute
設(shè)置
slider__size
的padding
的寬高
sliderSize.style.paddingTop = (bodyRatio * 100) + '%';
slider__controller做一個定位框
這里應(yīng)用 padding
屬性和 boder
屬性办素,以及 translate
屬性角雷。
- padding屬性保證定位框透明
- boder屬性設(shè)置邊框
- translate屬性控制定位框的移動
定位框的大小比例需要和window的長寬比例保持一致:
controller.style.paddingTop = (winRatio * 100) + '%';
當(dāng)頁面移動時,我們通過設(shè)置translate屬性值來定位到minimap上的位置摸屠。
controller.style.transform = `translate(${win.pageXOffset * realScale}px, ${win.pageYOffset * realScale}px)`;
為minimap增加事件效果
我們需要為minimap增加以下效果:
- 點(diǎn)擊minimap谓罗,主體內(nèi)容自動移動到指定位置
- 頁面滾動粱哼,minimap也隨之滾動
- 拖拽定位框季二,頁面也隨之進(jìn)行移動
一般的js程序員,基本會采用以下事件組合
- 鼠標(biāo)按下(mousedown)觸發(fā)移動(scrollTo)
- 鼠標(biāo)移動(mouseMove)觸發(fā)滾動(scrollBy)
- 鼠標(biāo)松開(mouseup)停止觸發(fā)滾動(scrollBy)
- 鼠標(biāo)不在minimap區(qū)域揭措,停止觸發(fā)移動(scrollTo)
let mouseX = 0,
mouseY = 0,
mouseDown = false;
function pointerDown(e) {
e.preventDefault();
mouseDown = true;
mouseX = e.clientX;
mouseY = e.clientY;
let offsetX = ((mouseX - slider.offsetLeft) - (controller.clientWidth/2)) / realScale;
let offsetY = ((mouseY - slider.offsetTop) - (controller.clientHeight / 2)) / realScale;
win.scrollTo(offsetX, offsetY);
}
slider.addEventListener('mousedown', pointerDown);
function pointerMove(e) {
if (mouseDown) {
x = e.clientX;
y = e.clientY;
win.scrollBy((x - mouseX) / realScale, ((y - mouseY) / realScale));
mouseX = x;
mouseY = y;
}
}
win.addEventListener('mousemove', pointerMove);
function pointerReset(e) {
mouseDown = false;
}
win.addEventListener('mouseup', pointerReset);
function pointerLeave(e) {
if ( e.target === body) {
mouseDown = false;
}
}
當(dāng)然胯舷,制作復(fù)雜頁面的minimap的時候,會涉及到更加復(fù)雜的邏輯交互绊含∩K唬可以采用reactJS來進(jìn)行事件的管理,這里不贅述躬充。有需要的同學(xué)可以留言逃顶,后期會考慮出一系列reactJS實(shí)際應(yīng)用的例子。