於本篇中,將介紹操控視窗與捲軸的一些方法。

透過 window 物件的 open 方法,可以幫你開啟一個新的視窗,並且設定該視窗的相關樣貌,例如:

以上的參數設定方式,除了 width 和 height 是以數字指定之外,其餘都是用 yes/no 或者 1/0 來指定是否呈現。另外,這些顯示效果根據瀏覽器的不同,可能也會有所不同,例如 Chorme 一定會顯示網址列和捲軸。以下範例會開啟一個寬度和高度各是螢幕一半的視窗:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>

function myOpen(){
	window.open(
		"../index.htm", "test",
		"width=" + screen.width / 2 + ", height=" + screen.height / 2
	);
}

</script>

<input type="button" onClick="myOpen();" value="按我開新視窗">

</body></html>

利用「close」方法,可以關閉視窗,範例如下:

<html>
<head></head>
<body bgcolor="#ccccff">

<input type="button" onClick="window.close();" value="按我關閉視窗">

</body></html>

在上述範例中,如果你先造訪了其他網頁再來到範例頁面,則會發現視窗無法關閉,這是基於安全性的考量,如果該視窗有瀏覽其他網頁的記錄(也就是你能按上一頁/下一頁的時候),則可能會無法關閉視窗。

透過 opener,可以操作原來的視窗。我們先使用一個網頁做為母視窗,用來開啟子視窗:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>

function myOpen(){
	window.open("js_window_opener_menu.htm", "test");
}

</script>

<input type="button" onClick="myOpen();" value="按我開新視窗">

</body></html>

子視窗的內容如下,此例以 opener.location.href 控制母視窗所開啟的網址:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>
function mainCtrl(theUrl){
	if(opener == null){
		alert("母視窗已關閉或者不存在");
	}
	else {
		opener.location.href = theUrl;
	}
}
</script>

<a href="javascript:mainCtrl('http://www.nthu.edu.tw')">清大首頁</a><br/>
<a href="javascript:mainCtrl('http://www.nctu.edu.tw')">交大首頁</a>

</body></html>

上述範例中,也可以改為在偵測到母視窗關閉後,重新開啟一個視窗,並改為對新的視窗操作。另外,各位也可以試試看直接開啟子視窗的效果。

如果希望呈現的效果不是子母視窗的互相操控,而是把一個網頁畫面進行切割,以將其他網頁內嵌進來互相操控,那麼可以透過 iframe 標籤進行(古早時代是利用 frameset 和 frame 標籤,但 HTML5 已不支援這兩個標籤)。此時,如果你要從某個 iframe 操作其他的 iframe,則除了利用超連結的 target 屬性以外,還可以使用 JavaScript 來進行。下面這個範例,用兩個 iframe 各自嵌入不同的網頁,並且可利用嵌入在左邊的網頁,操控嵌入在右邊的網頁,如下:

<html>
<head></head>
<body bgcolor="#ccccff">

<table width="100%" height="100%" border="1">
	<tr>
		<td width="20%">
			<iframe name="menu" src="js_window_frame_menu.htm" width="100%" height="100%"></iframe>
		</td>
		<td width="80%">
			<iframe name="right" src="js_window_frame_right.htm" width="100%" height="100%"></iframe>
		</td>
	</tr>
</table>

</body></html>

其中,被嵌入為左邊的網頁如下:

<html>
<head></head>
<body bgcolor="#ccccff">

<a href="http://www.nthu.edu.tw" target="right">清大首頁</a><br>
<a href="http://www.nctu.edu.tw" target="right">交大首頁</a><br>

<input type="button" value="原來那頁" onclick="parent.right.location.href='js_window_frame_right.htm';">

</body></html>

被嵌入為右邊的網頁則如下:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>
document.write(Math.random());
</script>

</body></html>

操作瀏覽紀錄也是在 windows 物件的業務範圍之內。你除了可透過 window.history.go 來前往上下幾頁以外,在 HTML5 中還加入了 pushState 等方法,可以用來加入和修改歷史記錄。如果搭配上物件樣式等方面的改變,則可以達成類似臉書點擊照片或者低卡點擊文章時的效果:

<html>
<head>
	<style>
		#slideShow {
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background: rgba(0, 0, 0, 0.5);
			display: none;
			z-index: 1;
		}
		#slideShowImg {
			position: absolute;
			top: 100px;
			left: 350px;
			z-index: 2;
			display: none;
		}
	</style>
</head>
<body bgcolor="#ccccff">

<script>
	function showSlide(path){
		document.getElementById("slideShowImg").src = path;
		document.getElementById("slideShow").style.display = "block";
		document.getElementById("slideShowImg").style.display = "block";
		window.history.pushState(
			{
				"what_is_this": "隨便放一些你自己認得出 state 的東西"
			},
			"這裡是標題,但是多數瀏覽器仍未支援",
			path
		);
	}
	
	function hideSlide(){
		document.getElementById("slideShow").style.display = "none";
		document.getElementById("slideShowImg").style.display = "none";
		window.history.back();
	}

	window.onpopstate = function(){hideSlide();}
</script>

<img src="../pics/bg01.jpg" width="300" onClick="showSlide('../pics/bg01.jpg')">

<div id="slideShow" onClick="hideSlide()"></div>
<img id="slideShowImg" src="">

</body></html>

若要改變已開啟視窗的位置或大小,可以使用 moveTo, moveBy, resizeTo, 以及 resizeBy。但請留意,除了 IE 之外的瀏覽器,這些函式只能對 window.open 開啟的視窗操作,若有興趣可自行測試:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>
function winCtrl(funcName){
	x = document.getElementById("X").value;
	y = document.getElementById("Y").value;
	eval("newWindow." + funcName + "(" + x + ", " + y + ");");
}
</script>

<form>
	<input type="button" value="先按我開新視窗" onclick="newWindow = window.open('../index.htm','OAO','width=300,height=300');">
	X: <input id="X" type="number" min="50" max="1000" value="50"><br/>
	Y: <input id="Y" type="number" min="50" max="1000" value="50"><br/>
	<input type="button" value="moveTo" onclick="winCtrl(this.value)">
	<input type="button" value="moveBy" onclick="winCtrl(this.value)">
	<input type="button" value="resizeTo" onclick="winCtrl(this.value)">
	<input type="button" value="resizeBy" onclick="winCtrl(this.value)">
</form>

</body></html>

若要控制視窗的捲動,可以使用 scroll, scrollTo, 以及 scrollBy:

<html>
<head></head>
<body bgcolor="#ccccff">

<script>
function winCtrl(funcName){
	x = document.getElementById("X").value;
	y = document.getElementById("Y").value;
	eval("window." + funcName + "(" + x + ", " + y + ");");
}
</script>

<form style="position:fixed;top:10px;left:10px;background:#cfc;padding:10px;">
	X: <input id="X" type="number" min="0" max="1000" value="50"><br/>
	Y: <input id="Y" type="number" min="0" max="1000" value="50"><br/>
	<input type="button" value="scroll" onclick="winCtrl(this.value)">
	<input type="button" value="scrollTo" onclick="winCtrl(this.value)">
	<input type="button" value="scrollBy" onclick="winCtrl(this.value)">
</form>

<div style="position:absolute;top:1500px;left:1500px;">
這是純粹把文件範圍撐大的 div
</div>

</body></html>

監聽 scroll 事件可以在捲軸捲動時做出你想要的處理,搭配上取得捲軸位置與物件高度的判斷,就可以做出無限捲動的效果:

<html>
<head>
	<style>
		table {
			width: 90%;
			border-collapse: collapse;
		}
		td {
			border: 1px solid;
			width: 15%;
			padding-top: 100px;
			padding-bottom: 100px;
			text-align: center;
		}
	</style>
</head>
<body bgcolor="#ccccff">

<center><table id="numTable"></table></center>

<script>

var begin = 1;

function getTr(){
	cnt = '<tr>';
	for(count=0; count<5; count++, begin++){
		cnt += '<td>' + begin + '</td>';
	}
	cnt += '</tr>';
	return cnt;
}

function makeNewRows(){
	document.getElementById('numTable').innerHTML += getTr();
    document.getElementById('numTable').innerHTML += getTr();
    document.getElementById('numTable').innerHTML += getTr();
    document.getElementById('numTable').innerHTML += getTr();
    document.getElementById('numTable').innerHTML += getTr();
}

window.onload = function(){
    document.getElementById('numTable').innerHTML = '';
    makeNewRows();
}

window.addEventListener('scroll', () => {
	if(window.innerHeight + window.pageYOffset > document.body.offsetHeight-5){
		makeNewRows();
	}
});
</script>

</body></html>

圖片延遲載入(lazy loading)的概念也非常類似,是等到圖片進入視窗範圍後再將其載入,以避免網頁在一開始就花時間在載入要捲很久才能看見的資源。Lazy loading 除了可以用已經在 Chrome 等部分瀏覽器支援的原生功能,或者第三方套件實作出來以外,也可以用比較基本的監聽事件來完成。在這個應用中,除了像無限捲動一樣,每當滑到底時再依次新增內容以外,也可以在每次捲動時,用 getBoundingClientRect 判斷圖片是否已經允許載入(為求範例簡潔故只針對高度做判斷,若需判斷寬度請依此類推):

<html>
<head></head>
<body bgcolor="#ccccff">

<center>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_01_魁力屋.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_02_橫綱.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_03_極雞.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_01_五行.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_02_若王子.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_03_天天有.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_04_arajin.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_05_boogie.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_01_珍遊.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_02_和釀.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_03_あくた川.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_04_山元.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1608_01_roppongi.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1608_02_池袋二郎.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1609_01_麵鬥庵.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1610_01_豬一.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1610_02_元喜神.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1612_01_若狹家.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1703_01_夕日.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1704_01_大文字.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1712_01_麵屋緣.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1802_01_小川.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1802_02_雞二.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1803_01_貓村.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1807_01_車廠.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1812_01_一燈.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1902_01_一慶.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1904_01_千雲.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1905_01_麵屋輝.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2007_01_京都柚子.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2007_02_中山.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2010_01_山下.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2011_01_你回來了.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2101_01_市民.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2102_01_隱家.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2103_01_柑橘.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2104_01_壹之穴.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2104_02_蘭丸.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2105_01_裸湯.JPG"><br>
</center>

<script>

function processLazyImgs(){
	lazyImgs = document.querySelectorAll('img.lazy');
	for(i=0; i<lazyImgs.length; i++){
		var pos = lazyImgs[i].getBoundingClientRect();
		if(pos.top >= 0 && pos.top <= window.innerHeight - 100){
			lazyImgs[i].src = lazyImgs[i].getAttribute('data-src');
			lazyImgs[i].classList.remove("lazy");
			lazyImgs[i].style.height = '500px';
			lazyImgs[i].style.margin = '5px';
		}
	}
}

window.onload = processLazyImgs
window.addEventListener('scroll', processLazyImgs);
</script>

</body></html>

我們也可以將上述範例,改成用 Intersection Observer API 來監視物件是否已經進入視窗範圍,這樣的好處是節省瀏覽器內部運算效能,你也不用自己撰寫相關的判斷

<html>
<head></head>
<body bgcolor="#ccccff">

<center>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_01_魁力屋.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_02_橫綱.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1605_03_極雞.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_01_五行.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_02_若王子.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_03_天天有.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_04_arajin.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1606_05_boogie.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_01_珍遊.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_02_和釀.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_03_あくた川.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1607_04_山元.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1608_01_roppongi.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1608_02_池袋二郎.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1609_01_麵鬥庵.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1610_01_豬一.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1610_02_元喜神.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1612_01_若狹家.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1703_01_夕日.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1704_01_大文字.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1712_01_麵屋緣.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1802_01_小川.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1802_02_雞二.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1803_01_貓村.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1807_01_車廠.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1812_01_一燈.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1902_01_一慶.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1904_01_千雲.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/1905_01_麵屋輝.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2007_01_京都柚子.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2007_02_中山.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2010_01_山下.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2011_01_你回來了.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2101_01_市民.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2102_01_隱家.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2103_01_柑橘.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2104_01_壹之穴.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2104_02_蘭丸.JPG"><br>
<img class="lazy" src="../pics/gray.png" data-src="../pics/ramen_and_cats/2105_01_裸湯.JPG"><br>
</center>

<script>

watcher = new IntersectionObserver((objs, observer) => {
	objs.forEach(obj => {
		if(obj.isIntersecting){
			img = obj.target;
			img.src = img.getAttribute('data-src');
			img.classList.remove("lazy");
			img.style.height = '500px';
			img.style.margin = '5px';
			observer.unobserve(img);
		}
	})
});
document.querySelectorAll('img.lazy').forEach(img => {
	watcher.observe(img);
});

</script>

</body></html>