#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

<<:  软件开发的用户需求哪里来?

>>:  不符合成本也买 Apple Search Ads 广告的原因

Day 7. 介绍一下VSCode-打code好帮手

Visual Studio Code(简称VS Code)是一个由微软开发的,同时支援Windows...

iOS APP 开发 OC 第二十二天,Portocol

tags: OC 30 day 什麽是Protocol? 作用:专门用来声明一大堆方法。(不能声明属...

《赖田捕手:追加篇》第 33 天:妥善运用 Heroku APP 暂存空间

第 33 天:妥善运用 Heroku APP 暂存空间 我很快的把书拿了起来,翻开封面。有一行我不认...

[Day 8]从零开始学习 JS 的连续-30 Days---阵列

宣告变数的资料型别--阵列 1.数值( Number ) 2.字串( String ) 3.布林值(...

dict查表进阶用法 - 使用部分字串查询是否存在任一key中

笔者在开发过程中遇到一个特殊状况, 需要搜寻的索引值,j是由某个数值中的部分数值组成 组成key的元...