今天挑戰的任務算是我蠻喜歡的一個小project,就是刻出四個slider來控制一個圖片的CSS屬性,包含perspective、rotateX、rotateY、rotateZ。當然這個也是參考其他教學資源(YT影片),而我另外用tailwindcss和vue composition api稍微改寫。
實作邏輯
原則上不外乎就是先規劃好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,
}
},
}