mirror of
https://github.com/7PH/powerglitch.git
synced 2026-06-16 12:34:00 +02:00
Initial commit
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
lib
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,2 @@
|
||||
# Demo website
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PowerGlitch</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
import OptionPanel from './components/OptionPanel.vue'
|
||||
import ImagePreview from './components/ImagePreview.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app h-full grid grid-cols-12">
|
||||
<div class="col-span-4 border p-4 overflow-y-auto">
|
||||
<OptionPanel />
|
||||
</div>
|
||||
<div class="col-span-8 flex flex-col">
|
||||
<div class="grow flex flex-col justify-center">
|
||||
<ImagePreview class="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pl-4 text-sm grid grid-cols-12">
|
||||
<div class="col-span-4 flex flex-col justify-center whitespace-nowrap overflow-x-hidden text-ellipsis min-w-0">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="col-span-8 flex gap-4 overflow-x-hidden">
|
||||
<select
|
||||
class="w-20"
|
||||
:value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value === 'true')"
|
||||
>
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { PowerGlitch } from '../../../src/index';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const container = ref(null);
|
||||
|
||||
const rebuild = () => {
|
||||
if (! container.value) {
|
||||
return;
|
||||
}
|
||||
PowerGlitch.install(container.value, appStore.powerGlitchOptions);
|
||||
};
|
||||
|
||||
// Rebuild when mounted or options changed
|
||||
onMounted(rebuild);
|
||||
watch(rebuild);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div ref="container" class="glitch"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.glitch {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
factor: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pl-4 text-sm grid grid-cols-12">
|
||||
<div class="col-span-4 flex flex-col justify-center whitespace-nowrap overflow-x-hidden text-ellipsis min-w-0">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="col-span-8 flex gap-4 overflow-x-hidden">
|
||||
<input
|
||||
class="w-20"
|
||||
:value="modelValue * factor"
|
||||
@change="$emit('update:modelValue', (parseInt($event.target.value) / factor))"
|
||||
type="number"
|
||||
:min="min * factor"
|
||||
:max="max * factor"
|
||||
/>
|
||||
<input
|
||||
:value="modelValue * factor"
|
||||
@input="$emit('update:modelValue', (parseInt($event.target.value) / factor))"
|
||||
type="range"
|
||||
:min="min * factor"
|
||||
:max="max * factor"
|
||||
:step="step * factor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { getDefaultOptions } from '../../../src/index.ts';
|
||||
import ToggleGroupOption from '@/components/ToggleGroupOption.vue';
|
||||
import StringOption from '@/components/StringOption.vue';
|
||||
import BooleanOption from '@/components/BooleanOption.vue';
|
||||
import NumberOption from '@/components/NumberOption.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4">
|
||||
|
||||
<div class="font-bold text-xl mb-4 flex">
|
||||
<div class="grow">PowerGlitch</div>
|
||||
<div>
|
||||
<a
|
||||
title="Github"
|
||||
target="_blank"
|
||||
href="https://github.com/7PH/powerglitch"
|
||||
>
|
||||
<fa icon="fa-brands fa-github" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="font-bold mt-6 mb-2 pl-2">Global</div>
|
||||
<StringOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.imageUrl"
|
||||
:title="'Image'"
|
||||
/>
|
||||
<StringOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.backgroundColor"
|
||||
:title="'Background color'"
|
||||
/>
|
||||
|
||||
<div class="font-bold mt-6 mb-2 pl-2">Timing</div>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.timing.duration"
|
||||
:title="'Loop duration (ms)'"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
:step="100"
|
||||
/>
|
||||
<ToggleGroupOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.timing.iterations"
|
||||
:title="'Loop'"
|
||||
:getDefaultValue="v => v ? Infinity : 1"
|
||||
/>
|
||||
<template v-if="appStore.powerGlitchOptions.timing.iterations < Infinity">
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.shake.velocity"
|
||||
:title="'Velocity (steps/s)'"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="font-bold mt-6 mb-2 pl-2">Glitch Time Span</div>
|
||||
<ToggleGroupOption
|
||||
v-model="appStore.powerGlitchOptions.glitchTimeSpan"
|
||||
:title="'Enabled'"
|
||||
:getDefaultValue="v => v ? getDefaultOptions().glitchTimeSpan : false"
|
||||
/>
|
||||
<template v-if="appStore.powerGlitchOptions.glitchTimeSpan">
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.glitchTimeSpan.start"
|
||||
:title="'Start time (%)'"
|
||||
:min="0.00"
|
||||
:max="1.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.glitchTimeSpan.end"
|
||||
:title="'End time (%)'"
|
||||
:min="0.00"
|
||||
:max="1.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="font-bold mt-6 mb-2 pl-2">Shake</div>
|
||||
<ToggleGroupOption
|
||||
v-model="appStore.powerGlitchOptions.shake"
|
||||
:title="'Enabled'"
|
||||
:getDefaultValue="v => v ? getDefaultOptions().shake : false"
|
||||
/>
|
||||
<template v-if="appStore.powerGlitchOptions.shake">
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.shake.velocity"
|
||||
:title="'Velocity (steps/s)'"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.shake.amplitudeX"
|
||||
:title="'X amplitude (%)'"
|
||||
:min="0.00"
|
||||
:max="2.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.shake.amplitudeY"
|
||||
:title="'Y amplitude (%)'"
|
||||
:min="0.00"
|
||||
:max="2.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="font-bold mt-6 mb-2 pl-2">Slices</div>
|
||||
<ToggleGroupOption
|
||||
v-model="appStore.powerGlitchOptions.slices"
|
||||
:title="'Enabled'"
|
||||
:getDefaultValue="v => v ? getDefaultOptions().slices : false"
|
||||
/>
|
||||
<template v-if="appStore.powerGlitchOptions.slices">
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.slices.count"
|
||||
:title="'Count (slices/step)'"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.slices.velocity"
|
||||
:title="'Velocity (steps/s)'"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.slices.minHeight"
|
||||
:title="'Min slice height (%)'"
|
||||
:min="0.01"
|
||||
:max="1.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
<NumberOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.slices.maxHeight"
|
||||
:title="'Max slice height (%)'"
|
||||
:min="0.01"
|
||||
:max="1.00"
|
||||
:step="0.01"
|
||||
:factor="100"
|
||||
/>
|
||||
<BooleanOption
|
||||
class="mt-1"
|
||||
v-model="appStore.powerGlitchOptions.slices.hueRotate"
|
||||
:title="'Hue rotate'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pl-4 text-sm grid grid-cols-12">
|
||||
<div class="col-span-4 flex flex-col justify-center whitespace-nowrap overflow-x-hidden text-ellipsis min-w-0">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="col-span-8 flex gap-4 overflow-x-hidden">
|
||||
<input
|
||||
class="w-full"
|
||||
:placeholder="title"
|
||||
:value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
getDefaultValue: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pl-4 text-sm grid grid-cols-12">
|
||||
<div class="col-span-4 flex flex-col justify-center whitespace-nowrap overflow-x-hidden text-ellipsis min-w-0">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="col-span-8 flex gap-4 overflow-x-hidden">
|
||||
<select
|
||||
class="w-20"
|
||||
:value="!! modelValue"
|
||||
@input="$emit('update:modelValue', getDefaultValue($event.target.value === 'true'))"
|
||||
>
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
import {
|
||||
faGithub,
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
library.add(
|
||||
faGithub,
|
||||
);
|
||||
|
||||
export default library;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import '@/style.css';
|
||||
import library from '@/icons';
|
||||
import App from '@/App.vue';
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(App)
|
||||
.use(pinia)
|
||||
.component('fa', FontAwesomeIcon)
|
||||
.mount('#app');
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import defaultImage from '@/assets/default.gif';
|
||||
import { getDefaultOptions } from '../../../src/index.ts';
|
||||
|
||||
|
||||
export const useAppStore = defineStore('main', {
|
||||
state: () => ({
|
||||
|
||||
/**
|
||||
* Default options for Power Glitch
|
||||
*/
|
||||
powerGlitchOptions: {
|
||||
...getDefaultOptions(),
|
||||
imageUrl: defaultImage,
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
|
||||
setOptions(options) {
|
||||
this.powerGlitchOptions = { ...this.powerGlitchOptions, ...options };
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: #242424;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
@apply bg-black text-white px-3 py-1 rounded-lg;
|
||||
}
|
||||
Generated
+2712
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "powerglitch",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs:dev": "vite",
|
||||
"docs:build": "vite build",
|
||||
"docs:preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.1",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"pinia": "^2.0.16",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"vite": "^3.0.0",
|
||||
"vue": "^3.2.37"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
export type PowerGlitchOptions = {
|
||||
imageUrl: string,
|
||||
backgroundColor: string,
|
||||
timing: {
|
||||
duration: number,
|
||||
iterations: number,
|
||||
easing?: string,
|
||||
},
|
||||
glitchTimeSpan: false | {
|
||||
start: number,
|
||||
end: number,
|
||||
},
|
||||
shake: false | {
|
||||
velocity: number,
|
||||
amplitudeX: number,
|
||||
amplitudeY: number,
|
||||
},
|
||||
slices: false | {
|
||||
count: number,
|
||||
velocity: number,
|
||||
minHeight: number,
|
||||
maxHeight: number,
|
||||
hueRotate: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
export type LayerDefinition = {
|
||||
steps: { [cssPropertyName: string]: string }[],
|
||||
timing: any,
|
||||
};
|
||||
|
||||
export type Rectangle = {
|
||||
top: number,
|
||||
left: number,
|
||||
height: number,
|
||||
width: number,
|
||||
};
|
||||
|
||||
export const getDefaultOptions = (): Partial<PowerGlitchOptions> => ({
|
||||
backgroundColor: 'transparent',
|
||||
timing: {
|
||||
duration: 2 * 1000,
|
||||
iterations: Infinity,
|
||||
},
|
||||
glitchTimeSpan: {
|
||||
start: 0.3,
|
||||
end: 0.7,
|
||||
},
|
||||
shake: {
|
||||
velocity: 20,
|
||||
amplitudeX: 0.5,
|
||||
amplitudeY: 0.5,
|
||||
},
|
||||
slices: {
|
||||
count: 6,
|
||||
velocity: 20,
|
||||
minHeight: 0.01,
|
||||
maxHeight: 0.05,
|
||||
hueRotate: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Get a random value between -1 and 1, which biases towards the center if the animation should not glitch at the given `stepPct` moment.
|
||||
*/
|
||||
const getGlitchRandom = (options: PowerGlitchOptions, stepPct: number) => {
|
||||
// Get glitch factor for this step
|
||||
let glitchFactor = 0;
|
||||
if (options.glitchTimeSpan) {
|
||||
const glitchStart = options.glitchTimeSpan.start;
|
||||
const glitchEnd = options.glitchTimeSpan.end;
|
||||
if (stepPct < glitchStart || stepPct > glitchEnd) {
|
||||
return 0;
|
||||
}
|
||||
const glitchPeak = glitchStart + (glitchEnd - glitchStart) / 2;
|
||||
if (stepPct < glitchPeak) {
|
||||
glitchFactor = (stepPct - glitchStart) / (glitchPeak - glitchStart);
|
||||
} else {
|
||||
glitchFactor = (glitchEnd - stepPct) / (glitchEnd - glitchPeak);
|
||||
}
|
||||
} else {
|
||||
glitchFactor = 1;
|
||||
}
|
||||
|
||||
// Apply glitch factor to a uniform random value between -1 and 1
|
||||
const rand = (Math.random() - .5) * 2;
|
||||
return rand * glitchFactor;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a random rectangle values in % to glitch. Rectangles never exceed 50% of the container height.
|
||||
*/
|
||||
const getRandomRectangle = ({ minHeight, maxHeight, minWidth, maxWidth }: { minHeight: number, maxHeight: number, minWidth: number, maxWidth: number }): Rectangle => {
|
||||
// Choose a random size for this rectangle
|
||||
const height = Math.floor(Math.random() * ((maxHeight - minHeight) * 100 + 1)) + minHeight * 100;
|
||||
const width = Math.floor(Math.random() * ((maxWidth - minWidth) * 100 + 1)) + minWidth * 100;
|
||||
|
||||
// Put this rectangle somewhere in the container so that it does not go out of the screen.
|
||||
const top = Math.floor(Math.random() * (100 - height));
|
||||
const left = Math.floor(Math.random() * (100 - width));
|
||||
|
||||
// Get value as a CSS polygon
|
||||
return { top, left, height, width };
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform a rectangle into a CSS polygon.
|
||||
*/
|
||||
const getRectanglePolygonCss = ({ top, left, height, width }: Rectangle) => {
|
||||
const topRight = `${left + width}% ${top}%`;
|
||||
const bottomRight = `${left + width}% ${top + height}%`;
|
||||
const bottomLeft = `${left}% ${top + height}%`;
|
||||
const topLeft = `${left}% ${top}%`;
|
||||
return `polygon(${topRight}, ${bottomRight}, ${bottomLeft}, ${topLeft})`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get default timing function
|
||||
*/
|
||||
const getDefaultTimingCss = (stepCount: number) => {
|
||||
return {
|
||||
easing: `steps(${stepCount}, jump-start)`,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate one layer
|
||||
*/
|
||||
const generateGlitchSliceLayer = (options: PowerGlitchOptions) => {
|
||||
if (! options.slices) {
|
||||
throw new Error('Slices are not enabled');
|
||||
}
|
||||
const stepCount = Math.floor(options.slices.velocity * options.timing.duration / 1000) + 1;
|
||||
const steps = [];
|
||||
for (let index = 0; index < stepCount; ++ index) {
|
||||
const rectangle = getRandomRectangle({ minHeight: options.slices.minHeight, maxHeight: options.slices.maxHeight, minWidth: 1, maxWidth: 1 });
|
||||
const translateX = getGlitchRandom(options, index / stepCount) * 30;
|
||||
const styles: {[cssPropertyName: string]: string} = {};
|
||||
styles.transform = `translate3d(${translateX}%, 0, 0)`;
|
||||
styles.clipPath = getRectanglePolygonCss(rectangle);
|
||||
if (options.slices.hueRotate) {
|
||||
styles.filter = `hue-rotate(${Math.floor(getGlitchRandom(options, index / stepCount) * 360)}deg)`;
|
||||
}
|
||||
steps.push(styles);
|
||||
};
|
||||
|
||||
return { steps, timing: { ...getDefaultTimingCss(stepCount), ...options.timing } };
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the base layer
|
||||
*/
|
||||
const generateBaseLayer = (options: PowerGlitchOptions): LayerDefinition => {
|
||||
|
||||
if (! options.shake) {
|
||||
return { steps: [], timing: getDefaultTimingCss(1) };
|
||||
}
|
||||
|
||||
const stepCount = Math.floor(options.shake.velocity * options.timing.duration / 1000) + 1;
|
||||
const steps = [];
|
||||
for (let index = 0; index < stepCount; ++ index) {
|
||||
const translateX = getGlitchRandom(options, index / stepCount) * options.shake.amplitudeX * 100;
|
||||
const translateY = getGlitchRandom(options, index / stepCount) * options.shake.amplitudeY * 100;
|
||||
const styles: {[cssPropertyName: string]: string} = {};
|
||||
styles.transform = `translate3d(${translateX}%, ${translateY}%, 0)`;
|
||||
steps.push(styles);
|
||||
};
|
||||
return { steps, timing: { ...getDefaultTimingCss(stepCount), ...options.timing } };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate all layers
|
||||
*/
|
||||
const generateLayers = (options: PowerGlitchOptions): LayerDefinition[] => {
|
||||
const layers = [];
|
||||
|
||||
layers.push(generateBaseLayer(options));
|
||||
|
||||
if (options.slices) {
|
||||
for (let i = 0; i < options.slices.count; ++ i) {
|
||||
layers.push(generateGlitchSliceLayer(options));
|
||||
}
|
||||
}
|
||||
|
||||
return layers;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const install = (elOrSelector: string | HTMLDivElement, options: PowerGlitchOptions) => {
|
||||
// Fix options with defaults
|
||||
options = { ...getDefaultOptions(), ...options };
|
||||
// Find selector or element
|
||||
if (typeof elOrSelector === 'string') {
|
||||
const element = document.querySelector<HTMLDivElement>(elOrSelector);
|
||||
if (! element) {
|
||||
throw new Error(`Could not find element with selector ${elOrSelector}`);
|
||||
}
|
||||
elOrSelector = element;
|
||||
}
|
||||
|
||||
// Prepare container
|
||||
elOrSelector.style.position = 'relative';
|
||||
elOrSelector.style.overflow = 'hidden';
|
||||
while (elOrSelector.firstChild) {
|
||||
elOrSelector.removeChild(elOrSelector.firstChild);
|
||||
}
|
||||
|
||||
// Fill layers
|
||||
const layers = generateLayers(options);
|
||||
for (const layer of layers) {
|
||||
const layerDiv = document.createElement('div');
|
||||
layerDiv.classList.add('layer');
|
||||
layerDiv.style.backgroundColor = options.backgroundColor;
|
||||
layerDiv.style.backgroundImage = `url(${options.imageUrl})`;
|
||||
layerDiv.style.backgroundRepeat = 'no-repeat';
|
||||
layerDiv.style.backgroundPosition = 'center';
|
||||
layerDiv.style.backgroundSize = 'contain';
|
||||
layerDiv.style.width = '100%';
|
||||
layerDiv.style.height = '100%';
|
||||
layerDiv.style.top = '0';
|
||||
layerDiv.style.left = '0';
|
||||
layerDiv.style.position = 'absolute';
|
||||
layerDiv.animate(layer.steps, layer.timing);
|
||||
elOrSelector.appendChild(layerDiv);
|
||||
}
|
||||
};
|
||||
|
||||
export const PowerGlitch = {
|
||||
install,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./docs-src/index.html",
|
||||
"./docs-src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"module": "commonjs",
|
||||
"outDir": "lib",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
root: 'docs-src/',
|
||||
build: {
|
||||
outDir: '../../dist',
|
||||
},
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './docs-src/src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user