1. 程式人生 > >CSS3&JavaScript 圖片分隔切換

CSS3&JavaScript 圖片分隔切換

在這裡插入圖片描述
這個切換效果在firebox上測試正常,但到了chrome上就沒有切換效果了。原因可能是它們在處理transition的流程上有差別,解決方案就是用animation來實現更精細的動畫。

(通常只有兩個狀態,這種簡單的情況適用transition比較方便;多個狀態就需要適用animation進行詳細的控制了)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8">
<style>
/* 
圖片分隔動畫有兩種思路
(1)大背景是一張靜圖,每個小方格做動畫
(2)連每個小方格都是靜態的,方格里面的偽元素做動畫
 */
/* 這裡提一下 column-count 這個屬性,他可以將一段文字分隔成多列,就跟報紙中的分欄一樣 */
.louver {
	width: 30em;
	height: 20em;
	border: 1px solid black;
	background-size: 100% 100%;
	position: relative;
	display: flex;
	/** flex-wrap: nowrap|wrap|wrap-reverse|initial|inherit; 預設值是nowrap */
	flex-wrap: wrap;
	overflow:hidden;
}
.louver>span {
	/** border: 1px solid black; */
	position: relative;
	/** width和height對於inline元素是無效的 */
	display:inline-block;
	/** 只有邊框佔據內部寬高,才能填滿整個父元素 */
	box-sizing: border-box;
	/** CSS3的attr 目前還只能用在偽元素裡,如果非要這麼用,建議使用LESS/SESS
	attr( <attr-name> <type-or-unit>? [ , <fallback> ]? ) 
	width:  calc(100% / attr(data-row px));
	height: calc(100% / attr(data-column px));
	*/
	overflow: hidden;
	border: 1px solid white;
}
.louver>span>span {
	position: absolute;
	width: 1.5em;
	height: 1.5em;
	color: black;
	background-color: white;
	border-radius: 50%;
	/** box-shadow不指定color的話,預設用的是元素的color */
	box-shadow: 0 0 .5em;
	display: flex;
	justify-content: center;
	align-items: center;
	bottom: .3em;
	left: 50%;
	transform: translateX(-50%);
	z-index: 1;
}
.louver>span::before, .louver>span::after {
	/** border: 1px solid red; */
	content: '';
	position: absolute;
	width: 100%;
	height: 100%;
	background-size: inherit;
	background-position: inherit;
}
</style>
<style>
.card-transition::before, .card-transition::after {
	transition: all 1s;
}
/* import表示優先生效,如果沒加important的話,如果兩個class都指定了相同的屬性哪個生效就說不清楚了
(一般是後面的覆蓋前面的,詳細的覆蓋模糊的【比如nth-child就比普通樣式優先順序高,即使它定義在前面】,行內樣式>內嵌>外部) */

.card-flip {
	/** preserve是保持;perspective是透視
	transform-style和perspective都是作用於它們的子元素的
	*/
	transform-style: preserve-3d;
	/** 透視距離必須和3d同時定義才會有3d效果 */
	perspective: 15em;
	/** overflow 屬性規定當內容溢位元素框時發生的事情。
	visible 預設值。內容不會被修剪,會呈現在元素框之外。
	hidden 	內容會被修剪,並且其餘內容是不可見的。
	scroll 	內容會被修剪,但是瀏覽器會顯示滾動條以便檢視其餘的內容。
	auto 	如果內容被修剪,則瀏覽器會顯示滾動條以便檢視其餘的內容。
	inherit 規定應該從父元素繼承 overflow 屬性的值。
	*/
	overflow: visible !important;
}
.card-flip::before, .card-flip::after {
	/** 隱藏背面 */
	backface-visibility: hidden;
}
.card-flip::after {
	transform: rotateY(180deg);
}
.card-flip-run::before {
	transform: rotateY(180deg) !important;
}
.card-flip-run::after {
	transform: rotateY(360deg) !important;
}
/* 推拉擠出 */
.card-push::before {
	left: 0;
}
.card-push::after {
	left: -100%;
}
.card-push-run::before {
	left: 100% !important;
}
.card-push-run::after {
	left: 0 !important;
}

/* 交叉推拉 */
.card-pushPull::before {
	left: 0;
}
/** odd表示奇數,會被翻譯為2n+1;even是偶數,會被翻譯為2n */
.card-pushPull:nth-child(odd)::after {
	left: -100%;
}
.card-pushPull:nth-child(even)::after {
	left: 100%;
}
.card-pushPull-run:nth-child(odd)::before {
	left: 100% !important;
}
.card-pushPull-run:nth-child(even)::before {
	left: -100% !important;
}
.card-pushPull-run::after {
	left: 0 !important;
}

/* 按下反彈 */
.card-pressSpring::before {
	top: 0;
}
.card-pressSpring:nth-child(odd)::after {
	top: -100%;
}
.card-pressSpring:nth-child(even)::after {
	top: 100%;
}
.card-pressSpring-run:nth-child(odd)::before {
	top: 100% !important;
}
.card-pressSpring-run:nth-child(even)::before {
	top: -100% !important;
}
.card-pressSpring-run::after {
	top: 0 !important;
}

/** 毛玻璃淡出
.card-blur::before, .card-blur::after {
	transition: transform .5s ease, filter 1s ease, opacity .5s ease;
}
*/
.card-blur::after {
	transform: scale(1.5, 1.5);
	filter: blur(10px);
	opacity: 0;
}
.card-blur-run::before {
	transform: scale(1, 1);
	filter: blur(0);
	opacity: 1;
}
.card-blur-run::before {
	transform: scale(1.5, 1.5) !important;
	filter: blur(10px) !important;
	opacity: 0 !important;
}
.card-blur-run::after {
	transform: scale(1, 1) !important;
	filter: blur(0) !important;
	opacity: 1 !important;
}
</style>
<script>
let imgs = [
"https://www.html5tricks.com/demo/SlidingImagePanels/images/1.jpg",
"https://www.html5tricks.com/demo/SlidingImagePanels/images/2.jpg",
"https://www.html5tricks.com/demo/SlidingImagePanels/images/3.jpg",
"https://www.html5tricks.com/demo/SlidingImagePanels/images/4.jpg"];
let containerClsName = "louver";
let animStyle;
let index = 0;
/*
background-size
	bg-size = [ <length> | <percentage> | auto ]{1,2} | cover | contain
	(1)按指定數值<length>、百分比<percentage>、原影象大小auto 顯示
	(2)cover 按比例調整圖片,使之正好能自適應整個背景區域的寬高比,長邊鋪滿短邊留白
	(3)contain 按比例調整圖片,使之剛好能自適應鋪滿整個背景區域,短邊鋪滿長邊溢位的部分被咔嚓
*/
function createCells() {
	//const louver = document.querySelector("." + containerClsName);
	const louver = document.getElementsByClassName(containerClsName)[0];
	//刪除其下的所有子元素
	louver.innerHTML = "";

	const row = louver.dataset.row;
	const column = louver.dataset.column;
	const count = row * column;
	const perWidth = louver.offsetWidth / column;
	const perHeight = louver.offsetHeight / row;
	
	//公共的數值寫在style裡
	const style = document.createElement("style");
	/*
	background-image
	url('URL') 	指向影象的路徑。
	none 		預設值。不顯示背景影象。
	inherit 	規定應該從父元素繼承 background-image 屬性的設定。
	*/
	style.textContent = "." + containerClsName + ">span{width:" +  100/column + "%;height:" + 100/row + "%;background-size:" + column*100 + "% " + row*100 + "%;}";
	for(let i = 1; i <= count; i++) {
		//nth-child 的第一個子元素的下標是1
		//background-position: left top
		style.appendChild(document.createTextNode("." + containerClsName + ">span:nth-child(" + i + "){background-position:" + ((i-1)%column)*-100 + "% " + parseInt((i-1)/column)*-100 + "%}"));
		//本來是在after裡面做序號,但如果這樣的話動畫就要另一個元素了
		//style.appendChild(document.createTextNode("." + containerClsName + ">span:nth-child(" + i + ")::before{content:'" + i + "'}"));
		const child = document.createElement("span");
		child.innerHTML = "<span>" + i + "</span>";
		louver.append(child);
	}
	document.head.appendChild(style);
	const backgroundUrlStyle = document.createElement("style");
	backgroundUrlStyle.id = "backgroundUrlStyle";
	document.head.appendChild(backgroundUrlStyle);
}

function setAnimStyle(_animStyle) {
	animStyle = _animStyle;
	const louvers = document.getElementsByClassName(containerClsName);
	for(let i=0; i<louvers.length; i++) {
		const childs = louvers[i].children;
		for(let j=0; j<childs.length; j++) {
			//由於classList沒有清空所有的方法,所以只能用className		
			childs[j].className = animStyle;
			//要臨時禁用動畫,必須先改變值,然後呼叫getComputedStyle,觸發recalculateStyle,之後再設定transition
			getComputedStyle(childs[j]).length;
			childs[j].classList.add("card-transition");
		}
	}
}
//該方法要求不執行過渡動畫
function setImage(index) {
	const next = (index+1) % imgs.length;
	const backgroundUrlStyle = document.getElementById("backgroundUrlStyle");
	backgroundUrlStyle.innerHTML="." + containerClsName + ">span::after{background-image:url(" + imgs[next] + ");}";
	backgroundUrlStyle.appendChild(document.createTextNode("." + containerClsName + ">span::before{background-image:url(" + imgs[index] + ");}"));
	/*
	const louvers = document.getElementsByClassName(containerClsName);
	for(let i=0;i<louvers.length; i++) {
		louvers[i].style.backgroundImage = "url(" + imgs[index] + ")";
	}
	*/
}
//該方法要求每次都執行過渡動畫
function changeImage() {
	setImage(index);
	setAnimStyle(animStyle);
	index = ++index % imgs.length;
	const e = window.event || arguments.callee.caller.arguments[0]; //火狐沒定義event,因此必須用這種方式做相容
	//currentTarget是繫結事件的元素;target是實際被點選的元素;事件從源冒泡到上層,除非呼叫了stopPropagation停止傳遞
	//e.currentTarget.classList.toggle("flip");
	//直接改父元素的class,然後在css中控制對應的子元素樣式也是可以的,但是這裡為了複用之前定義的css變數不這麼操作
	//JS forEach()方法不能用於HTMLCollection NodeList等類似於陣列但又不是真正的陣列。
	const childs = e.currentTarget.children;
	for(let i=0; i<childs.length; i++) {
		childs[i].classList.toggle(animStyle + "-run");
	};
}
function setListener() {
	document.querySelectorAll("input[name='animStyle']").forEach((elem)=>{
		elem.onchange=()=>{
			index = 0;
			setAnimStyle("card-" + elem.value);
			setImage(0);
		}
	});
	document.getElementsByName("numberVisible")[0].onchange = function() {
		const louvers = document.getElementsByClassName(containerClsName);
		for(let i=0; i<louvers.length; i++) {
			const childs = louvers[i].children;
			for(let j=0; j<childs.length; j++) {
				const firstChild = childs[j].firstChild;
				if(firstChild.localName == "span") {
					firstChild.style.display = this.checked ? "flex" : "none";
				}
			}
		}
	};
}
window.onload = ()=>{
	createCells();
	setImage(0);
	setListener();
	setAnimStyle("card-flip");
};
</script>
</head>

<body>
<div class="louver" data-row="2" data-column="4" onclick="changeImage()"></div>
<form action="" method="get" style="display:inline-block;">
	<input type="checkbox" name="numberVisible" checked="checked"><label for="flip">NumberVisible</label></input>
	<input type="radio" name="animStyle" value="flip" id="flip" checked="checked"><label for="flip">flip</label></input>
	<input type="radio" name="animStyle" value="push" id="push"><label for="push">push</label></input>
	<input type="radio" name="animStyle" value="pushPull" id="pushPull"><label for="pushPull">pushPull</label></input>
	<input type="radio" name="animStyle" value="pressSpring" id="pressSpring"><label for="pressSpring">pressSpring</label></input>
	<input type="radio" name="animStyle" value="blur" id="blur"><label for="blur">blur</label></input>
</form>
</body>
</html>