added CRT effect
This commit is contained in:
parent
a45066f9de
commit
912cbac798
8 changed files with 227 additions and 16 deletions
20
README.md
20
README.md
|
@ -22,3 +22,23 @@ Takes optional `options` configuration object:
|
|||
If no existing `canvasId` is provided then the effect will be applied to the entire browser screen.
|
||||
|
||||
You can animate alpha changes with `fg.setAlpha(newValue, step)` where `step` is optional and will determine how quickly the fade in/out will occur.
|
||||
|
||||
### CRT
|
||||
|
||||
Based on original code from [Alec Lownes](http://aleclownes.com/2017/02/01/crt-display.html).
|
||||
|
||||
```
|
||||
var crt = new Spooky.CRT({containerId: 'mydiv'});
|
||||
fg.execute();
|
||||
fg.stop();
|
||||
```
|
||||
|
||||
Takes required `options` configuration object:
|
||||
- `containerId`: id of the element to apply the CRT effect to (required)
|
||||
- `screenDoorColor`: color of the screen door effect (`#000000`)
|
||||
- `screenDoorSize`: size in pixels of the screen door effect (`2`)
|
||||
- `screenDoorOpacity`: opacity of the screen door effect from 0-1 (`0.25`)
|
||||
- `separationColor`: color of the separation effect (`#000000`)
|
||||
- `separationDistance`: horizontal movement distance in pixels of the separation effect (`5`)
|
||||
- `separationBlur`: blur size in pixels of the separation effect (`1`)
|
||||
- `separationOpacity`: opacity of the separation effect from 0-1 (`0.5`)
|
||||
|
|
59
package-lock.json
generated
59
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "spooky.js",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -31,6 +31,12 @@
|
|||
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.0.0.tgz",
|
||||
"integrity": "sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw==",
|
||||
"dev": true
|
||||
},
|
||||
"JSONStream": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
|
||||
|
@ -1178,6 +1184,24 @@
|
|||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
|
||||
|
@ -1260,6 +1284,25 @@
|
|||
"acorn-node": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.7.0.tgz",
|
||||
"integrity": "sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
"source-map-support": "~0.5.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
|
@ -1303,15 +1346,6 @@
|
|||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.2.tgz",
|
||||
"integrity": "sha512-zGVwKslUAD/EeqOrD1nQaBmXIHl1Vw371we8cvS8I6mYK9rmgX5tv8AAeJdfsQ3Kk5mGax2SVV/AizxdNGhl7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "~2.20.3"
|
||||
}
|
||||
},
|
||||
"umd": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
|
||||
|
@ -1372,6 +1406,11 @@
|
|||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
|
|
12
package.json
12
package.json
|
@ -1,23 +1,25 @@
|
|||
{
|
||||
"name": "spooky.js",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "Javascript/CSS effects to make a website spooky.",
|
||||
"scripts": {
|
||||
"build": "rm -rf ./build && tsc",
|
||||
"pack": "browserify build/unpacked/main.js --standalone Spooky > ./build/spooky.js",
|
||||
"minify": "uglifyjs build/spooky.js > build/spooky.min.js",
|
||||
"minify": "terser --compress -- build/spooky.js > build/spooky.min.js",
|
||||
"make": "npm run build && npm run pack && npm run minify"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"jquery": "^3.5.1"
|
||||
"jquery": "^3.5.1",
|
||||
"uuid": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/core-js": "^2.5.3",
|
||||
"@types/jquery": "^3.3.38",
|
||||
"@types/node": "^13.13.5",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"browserify": "^16.5.1",
|
||||
"typescript": "^3.8.3",
|
||||
"uglify-js": "^3.9.2"
|
||||
"terser": "^4.7.0",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
|
120
src/CRT.ts
Normal file
120
src/CRT.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
// credit: http://aleclownes.com/2017/02/01/crt-display.html
|
||||
|
||||
import * as $ from 'jquery';
|
||||
import * as uuid from 'uuid';
|
||||
import { IEffect } from './IEffect';
|
||||
import { rgbColor } from './Util';
|
||||
|
||||
export interface CRTOptions {
|
||||
containerId: string;
|
||||
screenDoorColor?: string;
|
||||
screenDoorSize?: number;
|
||||
screenDoorOpacity?: number;
|
||||
separationColor?: string;
|
||||
separationDistance?: number;
|
||||
separationBlur?: number;
|
||||
separationOpacity?: number;
|
||||
}
|
||||
|
||||
export class CRT implements IEffect {
|
||||
private containerId: string;
|
||||
private styleId: string;
|
||||
private wrapperId: string;
|
||||
|
||||
private separationColor: string;
|
||||
private separationDistance: number;
|
||||
private separationBlur: number;
|
||||
private separationOpacity: number;
|
||||
|
||||
constructor(options: CRTOptions) {
|
||||
this.containerId = options.containerId;
|
||||
this.styleId = `crt${uuid.v4().replace(/-/g, '')}`;
|
||||
this.wrapperId = `crt${uuid.v4().replace(/-/g, '')}`;
|
||||
|
||||
let screenDoorColor = options.screenDoorColor || '#000000';
|
||||
let screenDoorRgb = new rgbColor(screenDoorColor);
|
||||
let screenDoorSize = options.screenDoorSize || 2;
|
||||
if (screenDoorSize < 2) {
|
||||
screenDoorSize = 2;
|
||||
}
|
||||
let screenDoorOpacity = options.screenDoorOpacity || 0.25;
|
||||
|
||||
this.separationColor = options.separationColor || '#000000';
|
||||
this.separationDistance = options.separationDistance || 5;
|
||||
this.separationBlur = options.separationBlur || 1;
|
||||
this.separationOpacity = options.separationOpacity || 0.5;
|
||||
|
||||
const style = $('<style />').attr('id', this.styleId).text(`
|
||||
#${this.wrapperId} {
|
||||
position: relative;
|
||||
}
|
||||
#${this.containerId}::before {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
rgba(${screenDoorRgb.toString()}, 0) 50%,
|
||||
rgba(${screenDoorRgb.toString()}, ${screenDoorOpacity}) 50%
|
||||
), linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 0, 0, 0.06),
|
||||
rgba(0, 255, 0, 0.02),
|
||||
rgba(0, 0, 255, 0.06)
|
||||
);
|
||||
z-index: 2;
|
||||
background-size: 100% ${screenDoorSize}px, 3px 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
${this.makeFlickerFrames()}
|
||||
#${this.containerId}::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(18, 16, 16, 0.1);
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
animation: flicker_${this.styleId} 0.15s infinite;
|
||||
}
|
||||
${this.makeSeparationFrames()}
|
||||
#${this.containerId} {
|
||||
animation: separation_${this.styleId} 1.6s infinite;
|
||||
}
|
||||
`).appendTo($('body'));
|
||||
}
|
||||
|
||||
private makeFlickerFrames(): string {
|
||||
let css = `@keyframes flicker_${this.styleId} {`;
|
||||
for(let i = 0; i <= 20; i++) {
|
||||
css += ` ${i * 5}% { opacity: ${Math.random()}; }`;
|
||||
}
|
||||
css += ' }';
|
||||
return css;
|
||||
}
|
||||
|
||||
private makeSeparationFrames(): string {
|
||||
const rgb = new rgbColor(this.separationColor);
|
||||
let css = `@keyframes separation_${this.styleId} {`;
|
||||
for(let i = 0; i <= 20; i++) {
|
||||
css += ` ${i * 5}% { text-shadow: ${Math.random() * this.separationDistance}px 0 ${this.separationBlur}px rgba(${rgb.toString()}, ${this.separationOpacity}), -${Math.random() * this.separationDistance}px 0 ${this.separationBlur}px rgba(${rgb.toString()}, ${this.separationOpacity * 0.5}), 0 0 3px; }`;
|
||||
}
|
||||
css += ' }';
|
||||
return css;
|
||||
}
|
||||
|
||||
public execute(): void {
|
||||
$(`#${this.containerId}`).wrap($('<div />').attr('id', this.wrapperId));
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
$(`#${this.containerId}`).unwrap(`#${this.wrapperId}`);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// credit: https://codepen.io/zadvorsky/pen/PwyoMm
|
||||
|
||||
import * as $ from 'jquery';
|
||||
import { IEffect } from './IEffect';
|
||||
|
||||
export interface FilmGrainOptions {
|
||||
patternSize?: number;
|
||||
|
@ -10,7 +11,7 @@ export interface FilmGrainOptions {
|
|||
canvasId?: string;
|
||||
}
|
||||
|
||||
export class FilmGrain {
|
||||
export class FilmGrain implements IEffect {
|
||||
private patternSize: number;
|
||||
private grainScaleX: number;
|
||||
private grainScaleY: number;
|
||||
|
|
4
src/IEffect.ts
Normal file
4
src/IEffect.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface IEffect {
|
||||
execute(): void;
|
||||
stop(): void;
|
||||
}
|
24
src/Util.ts
Normal file
24
src/Util.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
export class rgbColor {
|
||||
public r: number = 0;
|
||||
public g: number = 0;
|
||||
public b: number = 0;
|
||||
|
||||
constructor(hex: string) {
|
||||
const regex: RegExp = hex.length > 4
|
||||
? /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
|
||||
: /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
const repeat = hex.length > 4 ? 1 : 2;
|
||||
|
||||
const result = regex.exec(hex);
|
||||
|
||||
if (result) {
|
||||
this.r = parseInt(result[1].repeat(repeat), 16);
|
||||
this.g = parseInt(result[2].repeat(repeat), 16);
|
||||
this.b = parseInt(result[3].repeat(repeat), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.r}, ${this.g}, ${this.b}`;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
CRT: require('./CRT.js').CRT,
|
||||
FilmGrain: require('./FilmGrain.js').FilmGrain,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue