iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Modern Web

30天30個前端任務系列 第 14

#15. CSS Perspective Slider(Vue版)

#15. CSS Perspective Slider

今天挑戰的任務算是我蠻喜歡的一個小project,就是刻出四個slider來控制一個圖片的CSS屬性,包含perspective、rotateX、rotateY、rotateZ。當然這個也是參考其他教學資源(YT影片),而我另外用tailwindcss和vue composition api稍微改寫。

Demo LinkGit Commit

實作邏輯
原則上不外乎就是先規劃好HTML結構圖,設定基礎CSS樣式,再來思考怎麼樣讓資料與模板綁定。最後再加上專案中引用到的css-doodle。讓我們來看PerspectivePlayground.vue這個檔案,以下拆分成template、script、style部分。

template
原則上我會將不重複的html tag直接用tailwind寫好樣式,像是外層的container,會重複地方就還是會運用SCSS來寫,比方說input、button。這純屬個人習慣...。

<template>
	<home-btn />
	<div
		class="
            relative
			flex flex-col
			items-center
			justify-start
            pt-28
            md:justify-center
            md:pt-0
			font-sans
			w-[100vw]
			h-[100vh]
			m-0
			bg-[#261c33]
            overflow-hidden
		"
	>
		<h2
			class="
				text-[#8d81f3] text-center
				font-extrabold
				m-5
				mt-0
				text-3xl
				md:text-5xl
			"
		>
			CSS Perspective Playground
		</h2>
		<main
			class="
				flex flex-col
				md:flex-row
				items-center
				h-[420px]
				w-[600px]
				font-serif
				text-[20px] text-white
			"
		>
			<section class="settings w-[50%] z-20">
				<div
					class="settings-container flex flex-col items-center md:items-start"
                >
                    <!-- 將perspective綁定在label和input當中 -->
					<label>perspective: {{ perspective }}px;</label>
					<input type="range" min="0" max="999" v-model="perspective" />
                    <!-- 綁定rotateX,以此類推 -->
					<label>rotateX: {{ rotateX }}deg; </label>
					<input type="range" min="-180" max="180" v-model="rotateX" />

					<label>rotateY: {{ rotateY }}deg; </label>
					<input type="range" min="-180" max="180" v-model="rotateY" />

					<label>rotateZ: {{ rotateZ }}deg; </label>
					<input type="range" min="-180" max="180" v-model="rotateZ" />
				</div>
				<div class="btnContainer flex flex-row justify-center mb-2 md:justify-start">
                    <!-- 按鈕部分綁定事件,會呼叫reset函式 -->
					<button id="resetBtn" type="button" @click.prevent="reset">
						Reset
					</button>
                    <!-- 按鈕部分綁定事件,會呼叫copy函式 -->
					<button id="copyBtn" type="button" @click.prevent="copy">Copy</button>
				</div>
			</section>
			<section class="output w-[50%] z-20">
				<div class="box-container p-[50px] border-2 border-[#8d81f3]">
					<div class="box" :style="box"></div>
				</div>
			</section>
		</main>
	</div>
    <!-- 安裝好css-doole後,可以在模板上直接使用css-doodle -->
    <!-- 基本上這是照抄官方範例,就不詳細解釋了 -->
	<css-doodle>
    :doodle {
    <!-- 這裡使用grid和positon absolute來覆蓋整個頁面-->
    @grid: 1x3 / 100vmax;
    position: absolute;
    top: 0; left: 0;
    z-index: 0;
    }

    @size: 100% 150%;
    position: absolute;

    background: @m(100, (
    linear-gradient(transparent, @p(
    #FFFDE1@repeat(2, @p([0-9a-f])),
    #FB3569@repeat(2, @p([0-9a-f]))
    ))
    @r(0%, 100%) @r(0%, 100%) /
    @r(1px) @r(23vmin)
    no-repeat
    ));

    will-change: transform;
    animation: f 50s linear calc(-50s / @size() * @i()) infinite;
    @keyframes f {
    from { transform: translateY(-100%) }
    to { transform: translateY(100%) }
    }
	</css-doodle>
</template>

style
將會複用的css選擇器獨立出來處理。

<style lang="scss" scoped>
main {
	label {
		color: white;
		display: block;
	}
	input[type='range'] {
		display: block;
		margin-bottom: 10px;
		width: 200px;
	}
	button {
		margin-bottom: 2px;
		width: 30%;
		background-color: #8d81f3;
		color: #fff;
		font-size: 20px;
		padding: 10px;
		outline: none;
		border: none;
		margin-right: 10px;
	}
	.output {
		.box-container {
        // .box主要用來和Vue做樣式綁定。
			.box {
				margin: auto;
				width: 150px;
				height: 150px;
				background: #8d81f3;
			}
		}
	}
}
</style>

script

// npm i css-doodle後,可以直接import進來,在模板上使用<css-doodle>
// 不需要另外安裝vue-css-doodle,也不用額外設定config。
import 'css-doodle'
import homeBtn from '../components/HomeBtn.vue'
import { computed, ref } from 'vue'

export default {
	name: '#15. CSS Perspective Playground',
	components: {
		homeBtn,
	},
	setup() {
        // 建立要被綁定的資料,用ref包裝起來
		const perspective = ref(100)
		const rotateX = ref(0)
		const rotateY = ref(0)
		const rotateZ = ref(0)
        
        // 將box函式帶入computed屬性,只要模板上的數值有變動,就執行宣告式渲染
		const box = computed(() => {
			return {
				transform: `
          perspective(${perspective.value}px)
          rotateX(${rotateX.value}deg)
          rotateY(${rotateY.value}deg)
          rotateZ(${rotateZ.value}deg)
        `,
			}
		})
        
        // 讓slider的input回到預設值
		const reset = () => {
            // 選定DOM
			const resetBtn = document.getElementById('resetBtn')
            // 讓DOM屬性和綁定資料回到reset狀態
			resetBtn.innerText = 'Done!'
			perspective.value = 100
			rotateX.value = 0
			rotateY.value = 0
			rotateZ.value = 0
            // 運用非同步語法,讓按鈕延遲0.8秒後回到原本狀態
			setTimeout(() => (resetBtn.innerText = 'Reset'), 800)
		}
        
        // 將當前的slider的input複製到剪貼簿上。
		const copy = () => {
			const copyBtn = document.getElementById('copyBtn')
			copyBtn.innerText = 'Copied!'
            // 建立一個DOM element <textarea>
			const el = document.createElement('textarea')
            // 讓textarea內的文字無法修改(唯獨)
			el.setAttribute('readonly', '')
            // 設好置入textarea內的文字
			el.value = `transform: ${box.value.transform}`
            // 將建立好的DOM插入到body當中
			document.body.appendChild(el)
            // 選擇建立好的DOM
			el.select()
            // 執行copy,將el.value複製到剪貼簿
			document.execCommand('copy')
            // 複製好之後移除el
			document.body.removeChild(el)
			setTimeout(() => (copyBtn.innerText = 'Copy'), 800)
		}

		return {
			perspective,
			rotateX,
			rotateY,
			rotateZ,
			box,
			reset,
			copy,
		}
	},
}

心得

  1. 透過這個小專案,會比較容易領會perspective和rotate之間的相互變化,不然光憑數字去做抽象思考,真的不太容易...。
  2. css-doodle很有趣,有興趣的朋友可以看一下袁川的CodePen

上一篇
#13. Split Landing Page(原生JS版), #14. RGB to Hex Converter(原生JS版)
下一篇
#16. Quiz App(原生JS版)
系列文
30天30個前端任務19

尚未有邦友留言

立即登入留言