神機錯亂包裝器 wrapper
包起來的元素會降低人性損耗,出現神機錯亂徵兆
技術關鍵字
| 名稱 | 描述 |
|---|---|
| SVG Filter | SVG 濾鏡效果,例如:模糊、扭曲、顏色調整等等 |
| SVG feTurbulence | 以 Perlin 雜訊產生紋理,可分別指定 x、y 方向的頻率,常拿來當位移圖 |
| SVG feDisplacementMap | 依位移圖的色值偏移取樣來源像素,可做出扭曲、凹陷、波動等效果 |
| IntersectionObserver | 偵測元素是否進入或離開視窗 |
使用範例
基本用法
包起來就會自己發作,也可以呼叫 glitch() 立即觸發。滑鼠移到按鈕上試試。
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center py-16">
<wrapper-cyberpsychosis ref="wrapperRef">
<base-btn
:label="t('button')"
@pointerenter="wrapperRef?.glitch()"
/>
</wrapper-cyberpsychosis>
</div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseBtn from '../../base-btn.vue'
import WrapperCyberpsychosis from '../wrapper-cyberpsychosis.vue'
const { t } = useI18n()
const wrapperRef = useTemplateRef('wrapperRef')
</script>表單驗證
輸入錯誤時呼叫 glitch(),錯越多次越不穩定。
NIGHT CITY ID // SCANR25
查看範例原始碼
vue
<template>
<div class="w-full flex justify-center py-12">
<div
class="cyber-panel"
:class="{
'is-error': errorCount > 0,
'is-success': succeeded,
}"
>
<div class="panel-header">
<span>NIGHT CITY ID // SCAN</span>
<span class="panel-tag">R25</span>
</div>
<label
class="field-label"
:for="inputId"
>
EMAIL_ADDRESS
</label>
<wrapper-cyberpsychosis
ref="inputWrapperRef"
:intensity="intensity"
:enabled="unstable"
:interval="[800, 2000]"
>
<div class="input-frame">
<!--
Chrome 會忽略 autocomplete="off",改給它不認得的值才不會自動填入;
data-* 分別讓 1Password、LastPass、Bitwarden 跳過這個欄位
-->
<input
:id="inputId"
v-model="email"
class="cyber-input"
placeholder="[email protected]"
autocomplete="cyber-id-scan"
data-1p-ignore
data-lpignore="true"
data-bwignore
@input="succeeded = false"
@keydown.enter="submit"
>
</div>
</wrapper-cyberpsychosis>
<div class="status">
<template v-if="message">
{{ succeeded ? '✓ ACCESS_GRANTED ::' : `▲ ERR_0${Math.min(errorCount, 9)} ::` }}
{{ message }}
</template>
</div>
<button
class="cyber-button"
type="button"
@click="submit"
>
{{ t('submit') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, useId, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import WrapperCyberpsychosis from '../wrapper-cyberpsychosis.vue'
const { t } = useI18n()
const EMAIL_PATTERN = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/
/** errorList 的句數,錯超過就停在最後一句 */
const ERROR_MESSAGE_COUNT = 5
const inputId = useId()
const inputWrapperRef = useTemplateRef('inputWrapperRef')
const email = ref('')
const errorCount = ref(0)
const succeeded = ref(false)
/** 錯越多次越不穩定 */
const intensity = computed(() => Math.min(1 + errorCount.value * 0.3, 2))
/** 錯三次後就開始三不五時自己閃現 */
const unstable = computed(() => errorCount.value >= 3)
const message = computed(() => {
if (succeeded.value) {
return t('success')
}
if (errorCount.value === 0) {
return ''
}
const index = Math.min(errorCount.value, ERROR_MESSAGE_COUNT) - 1
return t(`errorList[${index}]`)
})
function submit() {
if (EMAIL_PATTERN.test(email.value)) {
errorCount.value = 0
succeeded.value = true
return
}
errorCount.value++
inputWrapperRef.value?.glitch(600)
}
</script>原理
用 SVG 濾鏡把元素複製成幾個原色分身,往四周錯開、疊在本體上;再以 feTurbulence 產生橫條雜訊,交給 feDisplacementMap 做出輕微的切片錯位。
每次發作數百毫秒,每隔幾十毫秒換一格,偶爾恢復正常,訊號才有斷斷續續的感覺。沒發作時直接拿掉濾鏡,系統開啟「減少動態效果」時不會發作。
原始碼
API
Props
interface Props {
/**
* 是否自動隨機發作。關閉後仍可呼叫 `glitch()` 手動觸發。
*
* @default true
*/
enabled?: boolean;
/**
* 強度,影響分身間距與切片位移量
*
* @default 1
*/
intensity?: number;
/**
* 兩次發作的間隔範圍(ms)
*
* @default [1500, 4500]
*/
interval?: [number, number];
/**
* 每次發作的持續時間範圍(ms)
*
* @default [200, 700]
*/
duration?: [number, number];
}Methods
interface Expose {
/**
* 立即發作一次
*
* @param duration 持續時間(ms),預設從 `duration` 範圍隨機
*/
glitch: (duration?: number) => void;
}