@ -1,7 +1,5 @@ |
|||||||
import Vue from 'vue' |
import Vue from 'vue' |
||||||
import dragDialog from './dragDialog' |
import dragDialog from './dragDialog' |
||||||
import ripple from './ripple' |
|
||||||
|
|
||||||
Vue.directive('drag-dialog', dragDialog) |
Vue.directive('drag-dialog', dragDialog) |
||||||
Vue.directive('ripple', ripple) |
|
||||||
|
|
||||||
|
|||||||
@ -1,184 +0,0 @@ |
|||||||
//从quasar处搬运 https://quasar.dev/vue-directives/material-ripple
|
|
||||||
import './style.scss' |
|
||||||
|
|
||||||
let lastKeyCompositionStatus = false |
|
||||||
|
|
||||||
function isKeyCode(evt, keyCodes) { |
|
||||||
return ( |
|
||||||
lastKeyCompositionStatus === true || |
|
||||||
evt !== Object(evt) || |
|
||||||
evt.isComposing === true || |
|
||||||
evt.qKeyEvent === true |
|
||||||
) |
|
||||||
? false |
|
||||||
: [].concat(keyCodes).includes(evt.keyCode) |
|
||||||
} |
|
||||||
|
|
||||||
function css(element, css) { |
|
||||||
let style = element.style |
|
||||||
|
|
||||||
Object.keys(css).forEach(prop => { |
|
||||||
style[prop] = css[prop] |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function position(e) { |
|
||||||
if (e.touches && e.touches[0]) { |
|
||||||
e = e.touches[0] |
|
||||||
} |
|
||||||
else if (e.changedTouches && e.changedTouches[0]) { |
|
||||||
e = e.changedTouches[0] |
|
||||||
} |
|
||||||
else if (e.targetTouches && e.targetTouches[0]) { |
|
||||||
e = e.targetTouches[0] |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
top: e.clientY, |
|
||||||
left: e.clientX |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function stop(e) { |
|
||||||
e.stopPropagation() |
|
||||||
} |
|
||||||
|
|
||||||
const listenOpts = { |
|
||||||
hasPassive: false, |
|
||||||
passiveCapture: true, |
|
||||||
notPassiveCapture: true |
|
||||||
} |
|
||||||
|
|
||||||
function showRipple(evt, el, ctx, forceCenter) { |
|
||||||
ctx.modifiers.stop === true && stop(evt) |
|
||||||
|
|
||||||
let {center, color} = ctx.modifiers |
|
||||||
center = center === true || forceCenter === true |
|
||||||
|
|
||||||
const |
|
||||||
node = document.createElement('span'), |
|
||||||
innerNode = document.createElement('span'), |
|
||||||
pos = position(evt), |
|
||||||
{left, top, width, height} = el.getBoundingClientRect(), |
|
||||||
diameter = Math.sqrt(width * width + height * height), |
|
||||||
radius = diameter / 2, |
|
||||||
centerX = `${(width - diameter) / 2}px`, |
|
||||||
x = center ? centerX : `${pos.left - left - radius}px`, |
|
||||||
centerY = `${(height - diameter) / 2}px`, |
|
||||||
y = center ? centerY : `${pos.top - top - radius}px` |
|
||||||
|
|
||||||
innerNode.className = 'q-ripple__inner' |
|
||||||
css(innerNode, { |
|
||||||
height: `${diameter}px`, |
|
||||||
width: `${diameter}px`, |
|
||||||
transform: `translate3d(${x},${y},0) scale3d(.2,.2,1)`, |
|
||||||
opacity: 0 |
|
||||||
}) |
|
||||||
|
|
||||||
node.className = `q-ripple${color ? ' text-' + color : ''}` |
|
||||||
node.setAttribute('dir', 'ltr') |
|
||||||
node.appendChild(innerNode) |
|
||||||
el.appendChild(node) |
|
||||||
|
|
||||||
const abort = () => { |
|
||||||
node.remove() |
|
||||||
window.clearTimeout(timer) |
|
||||||
} |
|
||||||
ctx.abort.push(abort) |
|
||||||
|
|
||||||
let timer = window.setTimeout(() => { |
|
||||||
innerNode.classList.add('q-ripple__inner--enter') |
|
||||||
innerNode.style.transform = `translate3d(${centerX},${centerY},0) scale3d(1,1,1)` |
|
||||||
innerNode.style.opacity = 0.2 |
|
||||||
|
|
||||||
timer = window.setTimeout(() => { |
|
||||||
innerNode.classList.remove('q-ripple__inner--enter') |
|
||||||
innerNode.classList.add('q-ripple__inner--leave') |
|
||||||
innerNode.style.opacity = 0 |
|
||||||
|
|
||||||
timer = window.setTimeout(() => { |
|
||||||
node.remove() |
|
||||||
ctx.abort.splice(ctx.abort.indexOf(abort), 1) |
|
||||||
}, 275) |
|
||||||
}, 250) |
|
||||||
}, 50) |
|
||||||
} |
|
||||||
|
|
||||||
function updateCtx(ctx, {value, modifiers, arg}) { |
|
||||||
ctx.enabled = value !== false |
|
||||||
|
|
||||||
if (ctx.enabled === true) { |
|
||||||
ctx.modifiers = Object(value) === value |
|
||||||
? { |
|
||||||
stop: value.stop === true || modifiers.stop === true, |
|
||||||
center: value.center === true || modifiers.center === true, |
|
||||||
color: value.color || arg, |
|
||||||
keyCodes: [].concat(value.keyCodes || 13) |
|
||||||
} |
|
||||||
: { |
|
||||||
stop: modifiers.stop, |
|
||||||
center: modifiers.center, |
|
||||||
color: arg, |
|
||||||
keyCodes: [13] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'ripple', |
|
||||||
|
|
||||||
inserted(el, binding) { |
|
||||||
const ctx = { |
|
||||||
modifiers: {}, |
|
||||||
abort: [], |
|
||||||
|
|
||||||
click(evt) { |
|
||||||
if (ctx.enabled === true && evt.qSkipRipple !== true && evt.clientX >= 0) { |
|
||||||
showRipple(evt, el, ctx, evt.qKeyEvent === true) |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
keyup(evt) { |
|
||||||
if ( |
|
||||||
ctx.enabled === true && |
|
||||||
evt.qSkipRipple !== true && |
|
||||||
isKeyCode(evt, ctx.modifiers.keyCodes) === true |
|
||||||
) { |
|
||||||
showRipple(evt, el, ctx, true) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
updateCtx(ctx, binding) |
|
||||||
|
|
||||||
if (el.__qripple) { |
|
||||||
el.__qripple_old = el.__qripple |
|
||||||
} |
|
||||||
|
|
||||||
const computed = window.getComputedStyle(el) |
|
||||||
if (computed && computed.position === 'static') { |
|
||||||
el.style.position = 'relative' |
|
||||||
el.dataset.previousPosition = 'static' |
|
||||||
} |
|
||||||
|
|
||||||
el.__qripple = ctx |
|
||||||
el.addEventListener('click', ctx.click, listenOpts.passive) |
|
||||||
el.addEventListener('keyup', ctx.keyup, listenOpts.passive) |
|
||||||
}, |
|
||||||
|
|
||||||
update(el, binding) { |
|
||||||
el.__qripple !== void 0 && updateCtx(el.__qripple, binding) |
|
||||||
}, |
|
||||||
|
|
||||||
unbind(el) { |
|
||||||
const ctx = el.__qripple_old || el.__qripple |
|
||||||
if (ctx !== void 0) { |
|
||||||
ctx.abort.forEach(fn => { |
|
||||||
fn() |
|
||||||
}) |
|
||||||
el.removeEventListener('click', ctx.click, listenOpts.passive) |
|
||||||
el.removeEventListener('keyup', ctx.keyup, listenOpts.passive) |
|
||||||
delete el[el.__qripple_old ? '__qripple_old' : '__qripple'] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
.q-ripple { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
color: inherit; |
|
||||||
border-radius: inherit; |
|
||||||
z-index: 0; |
|
||||||
pointer-events: none; |
|
||||||
overflow: hidden; |
|
||||||
contain: strict; |
|
||||||
|
|
||||||
&__inner { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
opacity: 0; |
|
||||||
color: inherit; |
|
||||||
border-radius: 50%; |
|
||||||
background: currentColor; |
|
||||||
pointer-events: none; |
|
||||||
will-change: transform, opacity; |
|
||||||
|
|
||||||
&--enter { |
|
||||||
transition: transform .225s cubic-bezier(.4, 0, .2, 1), opacity .1s cubic-bezier(.4, 0, .2, 1) |
|
||||||
} |
|
||||||
|
|
||||||
&--leave { |
|
||||||
transition: opacity .25s cubic-bezier(.4, 0, .2, 1) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,34 +0,0 @@ |
|||||||
let insertEl = false |
|
||||||
|
|
||||||
const image = function ({index = 0, urlList}) { |
|
||||||
if (!window.lightGallery || !Array.isArray(urlList) || urlList.length < 1) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if (!insertEl) { |
|
||||||
const div = document.createElement('div') |
|
||||||
div.setAttribute('id', 'light-gallery-el') |
|
||||||
div.style.display = 'none' |
|
||||||
document.body.appendChild(div) |
|
||||||
div.addEventListener( |
|
||||||
'onCloseAfter', |
|
||||||
() => window.lgData[div.getAttribute('lg-uid')].destroy(true), |
|
||||||
false |
|
||||||
) |
|
||||||
insertEl = true |
|
||||||
} |
|
||||||
|
|
||||||
window.lightGallery(document.getElementById('light-gallery-el'), { |
|
||||||
index, |
|
||||||
download: false, |
|
||||||
dynamic: true, |
|
||||||
dynamicEl: urlList.map(src => ({src, thumb: src})) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
image.close = function () { |
|
||||||
if (!insertEl) return |
|
||||||
window.lgData[insertEl.getAttribute('lg-uid')].destroy(true) |
|
||||||
} |
|
||||||
|
|
||||||
export default image |
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 490 B |
|
Before Width: | Height: | Size: 961 B |
@ -1,44 +0,0 @@ |
|||||||
import Vue from 'vue' |
|
||||||
import Main from './main.vue' |
|
||||||
import {isEmpty} from "@/util" |
|
||||||
|
|
||||||
let Constructor = Vue.extend(Main) |
|
||||||
|
|
||||||
export default function ({left, top} = {}) { |
|
||||||
const instance = new Constructor() |
|
||||||
const position = getPosition({height: instance.height + 130, width: instance.width + 20, left, top}) |
|
||||||
|
|
||||||
instance.positionTop = position.top |
|
||||||
instance.positionLeft = position.left |
|
||||||
instance.$mount() |
|
||||||
document.body.appendChild(instance.$el) |
|
||||||
instance.visible = true |
|
||||||
|
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
instance.resolve = resolve |
|
||||||
instance.reject = reject |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getPosition({height, width, left, top} = {}) { |
|
||||||
const windowHeight = window.innerHeight |
|
||||||
const windowWidth = window.innerWidth |
|
||||||
|
|
||||||
//若任一位置信息为空,直接返回居中位置
|
|
||||||
if (isEmpty(left, top)) { |
|
||||||
return { |
|
||||||
top: center(windowHeight, height), |
|
||||||
left: center(windowWidth, width) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//判断位置是否超出屏幕外
|
|
||||||
if (windowWidth < left + width) left = center(windowWidth, width) |
|
||||||
if (windowHeight < top + height) top = center(windowHeight, height) |
|
||||||
|
|
||||||
return {left, top} |
|
||||||
} |
|
||||||
|
|
||||||
function center(full, part) { |
|
||||||
return Math.round((full - part) / 2) |
|
||||||
} |
|
||||||
@ -1,330 +0,0 @@ |
|||||||
<template> |
|
||||||
<transition :name="transition" @after-leave="handleAfterLeave"> |
|
||||||
<div |
|
||||||
v-show="visible" |
|
||||||
class="puzzle-container" |
|
||||||
:class="{'puzzle-shake':stat==='fail'}" |
|
||||||
:style="`left: ${positionLeft}px;top: ${positionTop}px;`" |
|
||||||
> |
|
||||||
<div class="puzzle-view" :style="`width:${width}px;height:${height}px;`"> |
|
||||||
<div v-show="!image.loading" style="width: 100%;height: 100%"> |
|
||||||
<img :src="image.src"/> |
|
||||||
|
|
||||||
<canvas ref="puzzleBox" :height="height" :width="width"/> |
|
||||||
|
|
||||||
<div class="puzzle-lost-box" :style="`left:${leftNum}px`"> |
|
||||||
<canvas ref="puzzleShadow" :style="sliderStyle" :height="height" :width="width"/> |
|
||||||
<canvas ref="puzzleLost" :style="sliderStyle" :height="height" :width="width"/> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<transition name="fade"> |
|
||||||
<div v-show="image.loading" class="puzzle-image-loading"> |
|
||||||
<div class="puzzle-image-loading__icon"/> |
|
||||||
<div class="puzzle-image-loading__text">加载中...</div> |
|
||||||
</div> |
|
||||||
</transition> |
|
||||||
|
|
||||||
<div :class="resultTipClass"> |
|
||||||
{{ stat === 'success' ? '验证通过' : '请正确拼合图像' }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="slider-container"> |
|
||||||
<div class="slider-track"> |
|
||||||
<div class="slider-tip" :style="stat==='ready'?'opacity: 1':''">拖动滑块完成拼图</div> |
|
||||||
</div> |
|
||||||
<div class="slider-btn" :style="sliderBtnStyle" @mousedown="moveStart" @touchstart="moveStart"/> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="puzzle-footer"> |
|
||||||
<div class="puzzle-small"> |
|
||||||
<a class="puzzle-text-container puzzle-close" @click="close"> |
|
||||||
<div class="puzzle-text-tip">关闭验证</div> |
|
||||||
</a> |
|
||||||
|
|
||||||
<a class="puzzle-text-container puzzle-refresh" @click="refresh"> |
|
||||||
<div class="puzzle-text-tip">刷新验证</div> |
|
||||||
</a> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</transition> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
import BtnStopImage from './image/btn_stop.png' |
|
||||||
import BtnMoveImage from './image/btn_move.png' |
|
||||||
|
|
||||||
/* |
|
||||||
* https://github.com/Kevin-269581661/vue-puzzle-verification |
|
||||||
* 网上搬的例子,实际开发建议用极验 |
|
||||||
* */ |
|
||||||
export default { |
|
||||||
name: "PuzzleVerify", |
|
||||||
|
|
||||||
props: { |
|
||||||
//动画名称,具体请查看transition.scss |
|
||||||
transition: { |
|
||||||
type: String, |
|
||||||
default: 'scale' |
|
||||||
}, |
|
||||||
// 画布图片的尺寸 |
|
||||||
width: { |
|
||||||
type: Number, |
|
||||||
default: 260 |
|
||||||
}, |
|
||||||
height: { |
|
||||||
type: Number, |
|
||||||
default: 160 |
|
||||||
}, |
|
||||||
// 滑块的大小 |
|
||||||
blockSize: { |
|
||||||
type: Number, |
|
||||||
default: 40 |
|
||||||
}, |
|
||||||
// 误差 |
|
||||||
deviation: { |
|
||||||
type: Number, |
|
||||||
default: 4 |
|
||||||
}, |
|
||||||
// 滑块随机出现的范围 |
|
||||||
padding: { |
|
||||||
type: Number, |
|
||||||
default: 80 |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
data() { |
|
||||||
return { |
|
||||||
visible: false, |
|
||||||
stat: 'ready',//ready,move,success,fail,error |
|
||||||
positionLeft: 0, |
|
||||||
positionTop: 0, |
|
||||||
resolve: () => ({}), |
|
||||||
reject: () => ({}), |
|
||||||
moveStartAtX: null, |
|
||||||
randomX: null, |
|
||||||
randomY: null, |
|
||||||
leftNum: 0, |
|
||||||
image: { |
|
||||||
src: '', |
|
||||||
loading: true |
|
||||||
}, |
|
||||||
sliderStyle: { |
|
||||||
left: '0', |
|
||||||
transition: 'inherit' |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
computed: { |
|
||||||
resultTipClass() { |
|
||||||
const showResultTip = ['success', 'fail'].includes(this.stat) |
|
||||||
return ['verify-result', `verify-${this.stat}`, showResultTip ? 'show-result' : ''] |
|
||||||
}, |
|
||||||
|
|
||||||
sliderBtnStyle() { |
|
||||||
return { |
|
||||||
...this.sliderStyle, |
|
||||||
'background-image': `url(${this.stat === 'move' ? BtnMoveImage : BtnStopImage})` |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
methods: { |
|
||||||
close() { |
|
||||||
this.visible = false |
|
||||||
this.clear() |
|
||||||
this.reject() |
|
||||||
}, |
|
||||||
|
|
||||||
refresh() { |
|
||||||
this.initImage().then(this.initCanvas) |
|
||||||
}, |
|
||||||
|
|
||||||
initCanvas() { |
|
||||||
this.clear() |
|
||||||
let MinX = this.padding + this.blockSize |
|
||||||
let MaxX = this.width - this.padding - 2 * this.blockSize - this.blockSize / 6 |
|
||||||
let MaxY = this.padding |
|
||||||
let MinY = this.height - this.padding - this.blockSize - this.blockSize / 6 |
|
||||||
this.randomX = Math.round(Math.random() * MaxX + MinX) |
|
||||||
this.randomY = Math.round(Math.random() * MaxY + MinY) |
|
||||||
let X = this.randomX |
|
||||||
let Y = this.randomY |
|
||||||
this.leftNum = -X + 10 |
|
||||||
let d = this.blockSize / 3 |
|
||||||
|
|
||||||
let c = this.$refs.puzzleBox |
|
||||||
let c_l = this.$refs.puzzleLost |
|
||||||
let c_s = this.$refs.puzzleShadow |
|
||||||
let ctx = c.getContext("2d") |
|
||||||
let ctx_l = c_l.getContext("2d") |
|
||||||
let ctx_s = c_s.getContext("2d") |
|
||||||
ctx.globalCompositeOperation = "xor" |
|
||||||
ctx.shadowBlur = 10 |
|
||||||
ctx.shadowColor = "#fff" |
|
||||||
ctx.shadowOffsetX = 3 |
|
||||||
ctx.shadowOffsetY = 3 |
|
||||||
ctx.fillStyle = "rgba(0,0,0,0.7)" |
|
||||||
ctx.beginPath() |
|
||||||
ctx.lineWidth = 1 |
|
||||||
ctx.strokeStyle = "rgba(0,0,0,0)" |
|
||||||
|
|
||||||
ctx.moveTo(X, Y) |
|
||||||
ctx.lineTo(X + d, Y) |
|
||||||
ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
|
||||||
ctx.lineTo(X + 3 * d, Y) |
|
||||||
ctx.lineTo(X + 3 * d, Y + d) |
|
||||||
ctx.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
|
||||||
ctx.lineTo(X + 3 * d, Y + 3 * d) |
|
||||||
ctx.lineTo(X, Y + 3 * d) |
|
||||||
|
|
||||||
ctx.closePath() |
|
||||||
ctx.stroke() |
|
||||||
ctx.fill() |
|
||||||
|
|
||||||
this.drawImage(img => ctx_l.drawImage(img, 0, 0, this.width, this.height)) |
|
||||||
|
|
||||||
ctx_l.beginPath() |
|
||||||
ctx_l.strokeStyle = "rgba(0,0,0,0)" |
|
||||||
|
|
||||||
ctx_l.moveTo(X, Y) |
|
||||||
ctx_l.lineTo(X + d, Y) |
|
||||||
ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
|
||||||
ctx_l.lineTo(X + 3 * d, Y) |
|
||||||
ctx_l.lineTo(X + 3 * d, Y + d) |
|
||||||
ctx_l.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
|
||||||
ctx_l.lineTo(X + 3 * d, Y + 3 * d) |
|
||||||
ctx_l.lineTo(X, Y + 3 * d) |
|
||||||
|
|
||||||
ctx_l.closePath() |
|
||||||
ctx_l.stroke() |
|
||||||
ctx_l.shadowBlur = 10 |
|
||||||
ctx_l.shadowColor = "black" |
|
||||||
ctx_l.clip() |
|
||||||
ctx_s.beginPath() |
|
||||||
ctx_s.lineWidth = 1 |
|
||||||
ctx_s.strokeStyle = "rgba(0,0,0,0)" |
|
||||||
|
|
||||||
ctx_s.moveTo(X, Y) |
|
||||||
ctx_s.lineTo(X + d, Y) |
|
||||||
ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y) |
|
||||||
ctx_s.lineTo(X + 3 * d, Y) |
|
||||||
ctx_s.lineTo(X + 3 * d, Y + d) |
|
||||||
ctx_s.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d) |
|
||||||
ctx_s.lineTo(X + 3 * d, Y + 3 * d) |
|
||||||
ctx_s.lineTo(X, Y + 3 * d) |
|
||||||
|
|
||||||
ctx_s.closePath() |
|
||||||
ctx_s.stroke() |
|
||||||
ctx_s.shadowBlur = 20 |
|
||||||
ctx_s.shadowColor = "black" |
|
||||||
ctx_s.fill() |
|
||||||
}, |
|
||||||
|
|
||||||
initImage() { |
|
||||||
const imageListTemp = [ |
|
||||||
"https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4018557288,1217151095&fm=26&gp=0.jpg", |
|
||||||
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3252521864,872614242&fm=26&gp=0.jpg", |
|
||||||
"https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg" |
|
||||||
] |
|
||||||
this.image.loading = true |
|
||||||
let imgRandomIndex = Math.round(Math.random() * (imageListTemp.length - 1)) |
|
||||||
this.image.src = imageListTemp[imgRandomIndex] |
|
||||||
return new Promise(resolve => resolve()) |
|
||||||
}, |
|
||||||
|
|
||||||
drawImage(fun) { |
|
||||||
let img = new Image() |
|
||||||
img.src = this.image.src |
|
||||||
img.onload = () => { |
|
||||||
fun(img) |
|
||||||
this.image.loading = false |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
clear() { |
|
||||||
this.stat = 'ready' |
|
||||||
let c = this.$refs.puzzleBox |
|
||||||
let c_l = this.$refs.puzzleLost |
|
||||||
let c_s = this.$refs.puzzleShadow |
|
||||||
c.setAttribute("height", c.getAttribute("height")) |
|
||||||
c_l.setAttribute("height", c.getAttribute("height")) |
|
||||||
c_s.setAttribute("height", c.getAttribute("height")) |
|
||||||
}, |
|
||||||
|
|
||||||
moveStart(e) { |
|
||||||
if (this.stat !== 'ready') return |
|
||||||
e.preventDefault() |
|
||||||
e.stopPropagation() |
|
||||||
this.moveStartAtX = e.type === 'mousedown' ? e.pageX : e.changedTouches[0].pageX |
|
||||||
this.addListener() |
|
||||||
}, |
|
||||||
|
|
||||||
moving(e) { |
|
||||||
this.stat = 'move' |
|
||||||
const pageX = e.type === 'mousemove' ? e.pageX : e.changedTouches[0].pageX |
|
||||||
const distance = pageX - this.moveStartAtX |
|
||||||
if (this.moveStartAtX === null |
|
||||||
|| distance < 0 |
|
||||||
|| distance > this.width - this.blockSize) { |
|
||||||
return |
|
||||||
} |
|
||||||
this.sliderStyle.left = distance + "px" |
|
||||||
this.sliderStyle.transition = "inherit" |
|
||||||
}, |
|
||||||
|
|
||||||
moveEnd(e) { |
|
||||||
const pageX = e.type === 'mouseup' ? e.pageX : e.changedTouches[0].pageX |
|
||||||
const distance = pageX - this.moveStartAtX |
|
||||||
const ver_Num = this.randomX - 10 |
|
||||||
const minLeft = ver_Num - this.deviation |
|
||||||
const maxLeft = ver_Num + this.deviation |
|
||||||
if (this.moveStartAtX !== null) { |
|
||||||
const success = maxLeft > distance && distance > minLeft |
|
||||||
this.stat = success ? 'success' : 'fail' |
|
||||||
window.setTimeout(() => { |
|
||||||
this.stat = 'ready' |
|
||||||
if (!success) return this.initCanvas() |
|
||||||
this.resolve() |
|
||||||
this.close() |
|
||||||
}, success ? 500 : 1000) |
|
||||||
} |
|
||||||
window.setTimeout(() => { |
|
||||||
this.sliderStyle.left = '0' |
|
||||||
this.sliderStyle.transition = "left 0.5s" |
|
||||||
}, 1000) |
|
||||||
this.moveStartAtX = null |
|
||||||
this.removeListener() |
|
||||||
}, |
|
||||||
|
|
||||||
addListener() { |
|
||||||
if (this.image.loading) return |
|
||||||
window.addEventListener("mousemove", this.moving) |
|
||||||
window.addEventListener("mouseup", this.moveEnd) |
|
||||||
window.addEventListener("touchmove", this.moving) |
|
||||||
window.addEventListener("touchend", this.moveEnd) |
|
||||||
}, |
|
||||||
|
|
||||||
removeListener() { |
|
||||||
window.removeEventListener("mousemove", this.moving) |
|
||||||
window.removeEventListener("mouseup", this.moveEnd) |
|
||||||
window.addEventListener("touchmove", this.moving) |
|
||||||
window.addEventListener("touchend", this.moveEnd) |
|
||||||
}, |
|
||||||
|
|
||||||
handleAfterLeave() { |
|
||||||
this.$destroy(true) |
|
||||||
this.$el.parentNode.removeChild(this.$el) |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
mounted() { |
|
||||||
this.refresh() |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<style lang="scss" src="./style.scss"></style> |
|
||||||
@ -1,216 +0,0 @@ |
|||||||
@import "~@/asset/style/variables.scss"; |
|
||||||
|
|
||||||
.puzzle-container { |
|
||||||
position: absolute; |
|
||||||
z-index: 1000; |
|
||||||
box-shadow: 0 0 10px #cccccc; |
|
||||||
border: 1px solid #cccccc; |
|
||||||
background: #ffffff; |
|
||||||
|
|
||||||
.puzzle-text-container { |
|
||||||
&:hover { |
|
||||||
overflow: visible !important; |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-text-tip { |
|
||||||
position: absolute; |
|
||||||
top: -32px; |
|
||||||
left: 10px; |
|
||||||
border-radius: 2px; |
|
||||||
padding: 0 4px; |
|
||||||
height: 22px; |
|
||||||
min-width: 50px; |
|
||||||
line-height: 22px; |
|
||||||
background-color: #5F5F5F; |
|
||||||
white-space: nowrap; |
|
||||||
font-size: 12px; |
|
||||||
text-align: center; |
|
||||||
color: white; |
|
||||||
|
|
||||||
&::before { |
|
||||||
display: block; |
|
||||||
position: absolute; |
|
||||||
bottom: -6px; |
|
||||||
left: 0; |
|
||||||
content: ''; |
|
||||||
border-style: solid; |
|
||||||
border-width: 4px 6px; |
|
||||||
border-color: #5F5F5F transparent transparent #5F5F5F; |
|
||||||
width: 0; |
|
||||||
height: 0; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-view { |
|
||||||
position: relative; |
|
||||||
overflow: hidden; |
|
||||||
margin: 10px 10px 0; |
|
||||||
|
|
||||||
.puzzle-image-loading { |
|
||||||
position: absolute; |
|
||||||
left: 0; |
|
||||||
top: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
background-color: #e5e5e5; |
|
||||||
padding-top: 10%; |
|
||||||
|
|
||||||
&__icon { |
|
||||||
/*background-size: 764.70588%; |
|
||||||
background-position: 0 70.70218%;*/ |
|
||||||
background-image: url(./image/loading.png); |
|
||||||
margin: 11% auto 10px; |
|
||||||
width: 34px; |
|
||||||
height: 26px; |
|
||||||
overflow: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
&__text { |
|
||||||
text-align: center; |
|
||||||
font-size: 14px; |
|
||||||
margin-bottom: 1%; |
|
||||||
color: #b2b2b2; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
img { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
object-fit: cover; |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-lost-box { |
|
||||||
position: absolute; |
|
||||||
left: 0; |
|
||||||
top: 0; |
|
||||||
} |
|
||||||
|
|
||||||
canvas { |
|
||||||
position: absolute; |
|
||||||
left: 0; |
|
||||||
top: 0; |
|
||||||
z-index: 22; |
|
||||||
} |
|
||||||
|
|
||||||
.verify-result { |
|
||||||
position: absolute; |
|
||||||
z-index: 999; |
|
||||||
color: white; |
|
||||||
height: 24px; |
|
||||||
line-height: 24px; |
|
||||||
left: 0; |
|
||||||
bottom: -25px; |
|
||||||
font-size: 14px; |
|
||||||
width: 100%; |
|
||||||
transition: bottom 0.3s; |
|
||||||
text-indent: 16px; |
|
||||||
|
|
||||||
&.verify-success { |
|
||||||
background-color: $--color-success |
|
||||||
} |
|
||||||
|
|
||||||
&.verify-fail { |
|
||||||
background-color: $--color-danger |
|
||||||
} |
|
||||||
|
|
||||||
&.show-result { |
|
||||||
bottom: 0; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.slider-container { |
|
||||||
position: relative; |
|
||||||
margin: 5.39% 3.24%; |
|
||||||
width: 93.52%; |
|
||||||
padding: 0 0 13.67% 0; |
|
||||||
background-color: #ffffff; |
|
||||||
background-image: url(./image/track.png); |
|
||||||
|
|
||||||
.slider-track { |
|
||||||
position: absolute; |
|
||||||
top: 50%; |
|
||||||
margin: -19px 0 0 0; |
|
||||||
padding: 0 0 0 25%; |
|
||||||
|
|
||||||
.slider-tip { |
|
||||||
position: relative; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
opacity: 0; |
|
||||||
transition: opacity .5s; |
|
||||||
line-height: 38px; |
|
||||||
font-size: 14px; |
|
||||||
text-align: center; |
|
||||||
white-space: nowrap; |
|
||||||
color: #88949d; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.slider-btn { |
|
||||||
position: absolute; |
|
||||||
margin: -4.62% 0 0 -2.31%; |
|
||||||
width: 25.38%; |
|
||||||
padding: 0 0 25.38% 0; |
|
||||||
cursor: pointer; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-footer { |
|
||||||
position: relative; |
|
||||||
border-top: 1px solid #EEEEEE; |
|
||||||
width: 100%; |
|
||||||
margin: 0; |
|
||||||
padding: 0 0 17.27% 0; |
|
||||||
|
|
||||||
.puzzle-small { |
|
||||||
position: absolute; |
|
||||||
left: 5.1%; |
|
||||||
top: 50%; |
|
||||||
margin-top: -4.13%; |
|
||||||
padding: 0 0 8.27% 0; |
|
||||||
width: 40.45%; |
|
||||||
height: 0; |
|
||||||
|
|
||||||
.puzzle-close, .puzzle-refresh { |
|
||||||
margin-left: 8.9%; |
|
||||||
width: 17.8%; |
|
||||||
cursor: pointer; |
|
||||||
text-decoration: none; |
|
||||||
vertical-align: top; |
|
||||||
position: relative; |
|
||||||
display: inline-block; |
|
||||||
height: 0; |
|
||||||
padding-bottom: 17.8%; |
|
||||||
overflow: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-close { |
|
||||||
margin-left: 0; |
|
||||||
background-image: url(./image/icon_close.png); |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-refresh { |
|
||||||
background: url(./image/icon_refresh_top.png) top center no-repeat, |
|
||||||
url(./image/icon_refresh_bottom.png) center bottom no-repeat; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.puzzle-shake { |
|
||||||
animation: shake 0.2s linear 3 both; |
|
||||||
} |
|
||||||
|
|
||||||
@keyframes shake { |
|
||||||
25% { |
|
||||||
transform: translate3d(-6px, 0, 0) |
|
||||||
} |
|
||||||
75% { |
|
||||||
transform: translate3d(6px, 0, 0) |
|
||||||
} |
|
||||||
100% { |
|
||||||
transform: translate3d(0, 0, 0) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import ImageViewer from './ImageViewer' |
|
||||||
import Verify from './Verify' |
|
||||||
|
|
||||||
export default function (Vue) { |
|
||||||
Vue.prototype.$image = ImageViewer |
|
||||||
Vue.prototype.$verify = Verify |
|
||||||
} |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
import Time from "./Time" |
|
||||||
|
|
||||||
export default class Particle { |
|
||||||
constructor(position, velocity = new Vector(), color = 'white', radius = 1, lifetime = 1, mass = 1) { |
|
||||||
this.position = position |
|
||||||
this.velocity = velocity |
|
||||||
this.color = color |
|
||||||
this.radius = radius |
|
||||||
this.lifetime = lifetime |
|
||||||
this.mass = mass |
|
||||||
|
|
||||||
this.isInCanvas = true |
|
||||||
this.createdOn = Time.now() |
|
||||||
} |
|
||||||
|
|
||||||
update(time) { |
|
||||||
if (!this.getRemainingLifetime()) return |
|
||||||
|
|
||||||
this.velocity.add(Particle.GRAVITATION.clone().multiplyScalar(this.mass)) |
|
||||||
this.position.add(this.velocity.clone().multiplyScalar(time.delta)) |
|
||||||
} |
|
||||||
|
|
||||||
render(canvas, context) { |
|
||||||
const remainingLifetime = this.getRemainingLifetime() |
|
||||||
|
|
||||||
if (!remainingLifetime) return |
|
||||||
|
|
||||||
const radius = this.radius * remainingLifetime |
|
||||||
|
|
||||||
context.globalAlpha = remainingLifetime |
|
||||||
context.globalCompositeOperation = 'lighter' |
|
||||||
context.fillStyle = this.color |
|
||||||
|
|
||||||
context.beginPath() |
|
||||||
context.arc(this.position.x, this.position.y, radius, 0, Math.PI * 2) |
|
||||||
context.fill() |
|
||||||
} |
|
||||||
|
|
||||||
getRemainingLifetime() { |
|
||||||
const elapsedLifetime = Time.now() - this.createdOn |
|
||||||
return Math.max(0, this.lifetime - elapsedLifetime) / this.lifetime |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
import Trail from "./Trail" |
|
||||||
|
|
||||||
export default class Rocket extends Trail { |
|
||||||
constructor(childFactory, explosionFactory, position, velocity = new Vector()) { |
|
||||||
super(childFactory, position, velocity) |
|
||||||
|
|
||||||
this.explosionFactory = explosionFactory |
|
||||||
this.lifetime = 10 |
|
||||||
} |
|
||||||
|
|
||||||
update(time) { |
|
||||||
if (this.getRemainingLifetime() && this.velocity.y > 0) { |
|
||||||
this.explosionFactory(this) |
|
||||||
this.lifetime = 0 |
|
||||||
} |
|
||||||
|
|
||||||
super.update(time) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
export default class Time { |
|
||||||
constructor() { |
|
||||||
const now = Time.now() |
|
||||||
this.delta = 0 |
|
||||||
this.elapsed = 0 |
|
||||||
this.start = now |
|
||||||
this.previous = now |
|
||||||
} |
|
||||||
|
|
||||||
update() { |
|
||||||
const now = Time.now() |
|
||||||
this.delta = now - this.previous |
|
||||||
this.elapsed = now - this.start |
|
||||||
this.previous = now |
|
||||||
} |
|
||||||
|
|
||||||
static now() { |
|
||||||
return Date.now() / 1000 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,42 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
import Particle from "./Particle" |
|
||||||
|
|
||||||
export default class Trail extends Particle { |
|
||||||
constructor(childFactory, position, velocity = new Vector(), lifetime = 1, mass = 1) { |
|
||||||
super(position, velocity) |
|
||||||
|
|
||||||
this.childFactory = childFactory |
|
||||||
this.children = [] |
|
||||||
this.lifetime = lifetime |
|
||||||
this.mass = mass |
|
||||||
|
|
||||||
this.isAlive = true |
|
||||||
} |
|
||||||
|
|
||||||
update(time) { |
|
||||||
super.update(time) |
|
||||||
|
|
||||||
// Add a new child on every frame
|
|
||||||
if (this.isAlive && this.getRemainingLifetime()) { |
|
||||||
this.children.push(this.childFactory(this)) |
|
||||||
} |
|
||||||
|
|
||||||
// Remove particles that are dead
|
|
||||||
this.children = this.children.filter(function (child) { |
|
||||||
if (child instanceof Trail) return child.isAlive |
|
||||||
|
|
||||||
return child.getRemainingLifetime() |
|
||||||
}) |
|
||||||
|
|
||||||
// Kill trail if all particles fade away
|
|
||||||
if (!this.children.length) this.isAlive = false |
|
||||||
|
|
||||||
// Update particles
|
|
||||||
this.children.forEach(child => child.update(time)) |
|
||||||
} |
|
||||||
|
|
||||||
render(canvas, context) { |
|
||||||
// Render all children
|
|
||||||
this.children.forEach(child => child.render(canvas, context)) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
export default class Vector { |
|
||||||
constructor(x = 0, y = 0) { |
|
||||||
this.x = x |
|
||||||
this.y = y |
|
||||||
} |
|
||||||
|
|
||||||
add(v) { |
|
||||||
this.x += v.x |
|
||||||
this.y += v.y |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
multiplyScalar(s) { |
|
||||||
this.x *= s |
|
||||||
this.y *= s |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
clone() { |
|
||||||
return new Vector(this.x, this.y) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,141 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
import Time from "./Time" |
|
||||||
import Particle from "./Particle" |
|
||||||
import Trail from "./Trail" |
|
||||||
import Rocket from "./Rocket" |
|
||||||
|
|
||||||
Particle.GRAVITATION = new Vector(0, 9.81) |
|
||||||
|
|
||||||
const getTrustParticleFactory = function () { |
|
||||||
function getColor() { |
|
||||||
const hue = Math.floor(Math.random() * 15 + 30) |
|
||||||
return `hsl(${hue}, 100%, 75%` |
|
||||||
} |
|
||||||
|
|
||||||
return function () { |
|
||||||
const position = this.position.clone() |
|
||||||
const velocity = this.velocity.clone().multiplyScalar(-.1) |
|
||||||
velocity.x += (Math.random() - .5) * 8 |
|
||||||
const color = getColor() |
|
||||||
const radius = 1 + Math.random() |
|
||||||
const lifetime = .5 + Math.random() * .5 |
|
||||||
const mass = .01 |
|
||||||
|
|
||||||
return new Particle(position, velocity, color, radius, lifetime, mass) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const getExplosionFactory = function (baseHue) { |
|
||||||
function getColor() { |
|
||||||
const hue = Math.floor(baseHue + Math.random() * 15) % 360 |
|
||||||
const lightness = Math.floor(Math.pow(Math.random(), 2) * 50 + 50) |
|
||||||
return `hsl(${hue}, 100%, ${lightness}%` |
|
||||||
} |
|
||||||
|
|
||||||
function getChildFactory() { |
|
||||||
return function (parent) { |
|
||||||
const direction = Math.random() * Math.PI * 2 |
|
||||||
const force = 8 |
|
||||||
const velocity = new Vector(Math.cos(direction) * force, Math.sin(direction) * force) |
|
||||||
const color = getColor() |
|
||||||
const radius = 1 + Math.random() |
|
||||||
const lifetime = 1 |
|
||||||
const mass = .1 |
|
||||||
|
|
||||||
return new Particle(parent.position.clone(), velocity, color, radius, lifetime, mass) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function getTrail(position) { |
|
||||||
const direction = Math.random() * Math.PI * 2 |
|
||||||
const force = Math.random() * 128 |
|
||||||
const velocity = new Vector(Math.cos(direction) * force, Math.sin(direction) * force) |
|
||||||
const lifetime = .5 + Math.random() |
|
||||||
const mass = .075 |
|
||||||
|
|
||||||
return new Trail(getChildFactory(), position, velocity, lifetime, mass) |
|
||||||
} |
|
||||||
|
|
||||||
return function (parent) { |
|
||||||
let trails = 32 |
|
||||||
while (trails--) { |
|
||||||
parent.children.push(getTrail(parent.position.clone())) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default class Firework { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext('2d') |
|
||||||
this.time = new Time() |
|
||||||
this.rockets = [] |
|
||||||
this.rAF = null |
|
||||||
this.timer = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.addRocket = this.addRocket.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
window.addEventListener('click', this.addRocket) |
|
||||||
this.timer = window.setInterval(this.addRocket, 1000) |
|
||||||
this.resize() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
window.removeEventListener('click', this.addRocket) |
|
||||||
this.stopSign = true |
|
||||||
this.rockets = [] |
|
||||||
this.time = undefined |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
this.timer && window.clearInterval(this.timer) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.time.update() |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
|
|
||||||
for (let i = 0; i < this.rockets.length; i++) { |
|
||||||
this.rockets[i].update(this.time) |
|
||||||
this.rockets[i].render(this.canvas, this.ctx) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
addRocket() { |
|
||||||
const trustParticleFactory = getTrustParticleFactory() |
|
||||||
const explosionFactory = getExplosionFactory(Math.random() * 360) |
|
||||||
|
|
||||||
const position = new Vector(Math.random() * this.canvas.width, this.canvas.height) |
|
||||||
const thrust = window.innerHeight * .75 |
|
||||||
const angle = Math.PI / -2 + (Math.random() - .5) * Math.PI / 8 |
|
||||||
const velocity = new Vector(Math.cos(angle) * thrust, Math.sin(angle) * thrust) |
|
||||||
const lifetime = 3 |
|
||||||
|
|
||||||
this.rockets.push(new Rocket(trustParticleFactory, explosionFactory, position, velocity, lifetime)) |
|
||||||
|
|
||||||
this.rockets = this.rockets.filter(rocket => rocket.isAlive) |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,95 +0,0 @@ |
|||||||
function mountainHeight(position, roughness) { |
|
||||||
let frequencies = [1721, 947, 547, 233, 73, 31, 7] |
|
||||||
return frequencies.reduce((height, freq) => height * roughness - Math.cos(freq * position), 0) |
|
||||||
} |
|
||||||
|
|
||||||
export default class Godrays { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext('2d') |
|
||||||
this.frame = 0 |
|
||||||
this.godraysCanvas = canvas.cloneNode() |
|
||||||
this.godraysCtx = this.godraysCanvas.getContext('2d') |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
this.resize() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
this.stopSign = true |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
this.godraysCtx.clearRect(0, 0, this.godraysCanvas.width, this.godraysCanvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.canvas.width = 512 |
|
||||||
this.canvas.height = 256 |
|
||||||
this.godraysCanvas.width = 128 |
|
||||||
this.godraysCanvas.height = 64 |
|
||||||
let sunY = Math.cos(this.frame++ / 512) * 24 |
|
||||||
let emissionGradient = this.godraysCtx.createRadialGradient( |
|
||||||
this.godraysCanvas.width / 2, this.godraysCanvas.height / 2 + sunY, // The sun's center.
|
|
||||||
0, // Start radius.
|
|
||||||
this.godraysCanvas.width / 2, this.godraysCanvas.height / 2 + sunY, // Sun's center again.
|
|
||||||
44 // End radius.
|
|
||||||
) |
|
||||||
this.godraysCtx.fillStyle = emissionGradient |
|
||||||
emissionGradient.addColorStop(.1, '#0C0804') // Color for pixels in radius 0 to 4.4 (44 * .1).
|
|
||||||
emissionGradient.addColorStop(.2, '#060201') // Color for everything past radius 8.8.
|
|
||||||
this.godraysCtx.fillRect(0, 0, this.godraysCanvas.width, this.godraysCanvas.height) |
|
||||||
this.godraysCtx.fillStyle = '#000' |
|
||||||
let skyGradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height) |
|
||||||
skyGradient.addColorStop(0, '#2a3e55') // Blueish at the top.
|
|
||||||
skyGradient.addColorStop(.7, '#8d4835') // Reddish at the bottom.
|
|
||||||
this.ctx.fillStyle = skyGradient |
|
||||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) { |
|
||||||
this.ctx.fillStyle = `hsl(7, 23%, ${23 - i * 6}%)` |
|
||||||
for (let x = this.canvas.width; x--;) { |
|
||||||
let mountainPosition = (this.frame + this.frame * i * i) / 3000 + x / 2000 |
|
||||||
let mountainRoughness = i / 19 - .5 |
|
||||||
let y = 128 + i * 25 + mountainHeight(mountainPosition, mountainRoughness) * 45 |
|
||||||
this.ctx.fillRect(x, y, 1, 999) |
|
||||||
this.godraysCtx.fillRect(x / 4, y / 4 + 1, 1, 999) |
|
||||||
} |
|
||||||
} |
|
||||||
this.ctx.globalCompositeOperation = this.godraysCtx.globalCompositeOperation = 'lighter' |
|
||||||
for (let scaleFactor = 1.07; scaleFactor < 5; scaleFactor *= scaleFactor) { |
|
||||||
this.godraysCtx.drawImage( |
|
||||||
this.godraysCanvas, |
|
||||||
(this.godraysCanvas.width - this.godraysCanvas.width * scaleFactor) / 2, |
|
||||||
(this.godraysCanvas.height - this.godraysCanvas.height * scaleFactor) / 2 - sunY * scaleFactor + sunY, |
|
||||||
this.godraysCanvas.width * scaleFactor, |
|
||||||
this.godraysCanvas.height * scaleFactor |
|
||||||
) |
|
||||||
} |
|
||||||
this.ctx.drawImage(this.godraysCanvas, 0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,261 +0,0 @@ |
|||||||
// Currently in Chrome you need to click the "Get Adobe Flash" button so it'll ask you if you want to
|
|
||||||
// allow flash to run.
|
|
||||||
|
|
||||||
// The rest of this is formatted as:
|
|
||||||
// // Explanation of the compressed code
|
|
||||||
// // ...
|
|
||||||
//
|
|
||||||
// // Commented out, compressed code
|
|
||||||
//
|
|
||||||
// Readable (more or less) version of the code
|
|
||||||
// ...
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", go) |
|
||||||
|
|
||||||
function go() { |
|
||||||
"use strict" |
|
||||||
// JS1K's HTML shim gives us a canvas (a) and its 2D context (c) for free. We'll set them up here.
|
|
||||||
|
|
||||||
let canvas = document.querySelector('canvas') |
|
||||||
let ctx = canvas.getContext('2d') |
|
||||||
|
|
||||||
// First off - we define an abbreviation function. This takes an object, iterates over its properties
|
|
||||||
// and stores their names as strings in a 2 or 3 letter variable ("this" is the window object).
|
|
||||||
//
|
|
||||||
// p[0]+p[6] will evaluate to the 1st and 7th letter (or the 1st+"undefined" if there's no 7th),
|
|
||||||
// [p[20]] will be an empty string if the property's name is too short ([undefined] gets coerced to
|
|
||||||
// an empty string).
|
|
||||||
//
|
|
||||||
// This is a variation on Marijn Haverbeke's technique - see https://marijnhaverbeke.nl/js1k/
|
|
||||||
//
|
|
||||||
// We won't be using it in the readable version of the demo.
|
|
||||||
|
|
||||||
// A=o=>{for(p in o)this[p[0]+p[6]+[p[20]]]=p}
|
|
||||||
|
|
||||||
// Next we abbreviate all the properties in our window object because requestAnimationFrame() is
|
|
||||||
// kind of long. We can't call A(window) because it will try to abbreviate all our abbreviations (since
|
|
||||||
// it stores them in the window object) so we'll use it on "top" which has the same properties.
|
|
||||||
// We really just need a shorter requestAnimationFrame().
|
|
||||||
//
|
|
||||||
// Sidenote: this is a clear violation of JS1K rules, which is why it's very important not to read them
|
|
||||||
// before the competition is over.
|
|
||||||
|
|
||||||
// A(top)
|
|
||||||
|
|
||||||
// Now, since our demo is fairly heavy we use a small canvas, but we want it to be fullscreen on a
|
|
||||||
// black background, so we waste ~90 bytes on some CSS to stretch it (currently "object-fit:contain"
|
|
||||||
// doesn't work for canvas on MS browsers).
|
|
||||||
//
|
|
||||||
// To avoid wasting 90 bytes just on this, we take this opportunity to define P and Q as 'width' and
|
|
||||||
// 'height' for later. This is probably a mistake since I ended up packing it with regpack anyway.
|
|
||||||
//
|
|
||||||
// The weird bit at the end is an ES6 template literal being abused to call the array's join method
|
|
||||||
// with something that will be coerced into the string ':100%;'.
|
|
||||||
|
|
||||||
// a.style=[P='width',Q='height','object-fit:contain;background:#000'].join`:100%;`
|
|
||||||
|
|
||||||
canvas.style = 'width: 100%; height: 100%; object-fit:contain; background:#000;' |
|
||||||
|
|
||||||
// Now we need a frame counter.
|
|
||||||
|
|
||||||
// t=0
|
|
||||||
|
|
||||||
let frame = 0 |
|
||||||
|
|
||||||
// B() is the requestAnimationFrame callback.
|
|
||||||
|
|
||||||
// B=_=>{
|
|
||||||
|
|
||||||
function onFrame() { |
|
||||||
// Set width and height on our canvases, we'll be using a smaller canvas for the godrays. This
|
|
||||||
// also clears and resets their states. While we're at it, we'll store their dimensions in one
|
|
||||||
// letter vars for later.
|
|
||||||
|
|
||||||
// w=a[P]=512
|
|
||||||
// h=a[Q]=256
|
|
||||||
// W=E[P]=128
|
|
||||||
// H=E[Q]=64
|
|
||||||
|
|
||||||
canvas.width = 512 |
|
||||||
canvas.height = 256 |
|
||||||
godraysCanvas.width = 128 |
|
||||||
godraysCanvas.height = 64 |
|
||||||
|
|
||||||
// Set the sun's vertical position.
|
|
||||||
|
|
||||||
// T=C(t++/w)*24
|
|
||||||
|
|
||||||
let sunY = Math.cos(frame++ / 512) * 24 // This is actually the offset from the middle of the canvas. |
|
||||||
|
|
||||||
// Get the 2D context for our godrays canvas, and create abbreviations for all the context properties.
|
|
||||||
|
|
||||||
// A(F=E.getContext`2d`)
|
|
||||||
|
|
||||||
let godraysCtx = godraysCanvas.getContext('2d') |
|
||||||
|
|
||||||
// Now we set the godrays' context fillstyle (window.fy is 'fillStyle') to a newly created gradient
|
|
||||||
// (cr is 'createRadialGradient') which we also run through our abbreviator.
|
|
||||||
|
|
||||||
// A(F[fy]=g=F[cR](H,32+T,0,H,32+T,44)) // Could have shaved one more char here...
|
|
||||||
|
|
||||||
let emissionGradient = godraysCtx.createRadialGradient( |
|
||||||
godraysCanvas.width / 2, godraysCanvas.height / 2 + sunY, // The sun's center.
|
|
||||||
0, // Start radius.
|
|
||||||
godraysCanvas.width / 2, godraysCanvas.height / 2 + sunY, // Sun's center again.
|
|
||||||
44 // End radius.
|
|
||||||
) |
|
||||||
godraysCtx.fillStyle = emissionGradient |
|
||||||
|
|
||||||
// Now we addColorStops. This needs to be a dark gradient because our godrays effect will basically
|
|
||||||
// overlay it on top of itself many many times, so anything lighter will result in lots of white.
|
|
||||||
//
|
|
||||||
// If you're not space-bound you can add another stop or two, maybe fade out to black, but this
|
|
||||||
// actually looks good enough.
|
|
||||||
|
|
||||||
// g[ao](.1,'#0C0804')
|
|
||||||
// g[ao](.2,'#060201')
|
|
||||||
|
|
||||||
emissionGradient.addColorStop(.1, '#0C0804') // Color for pixels in radius 0 to 4.4 (44 * .1).
|
|
||||||
emissionGradient.addColorStop(.2, '#060201') // Color for everything past radius 8.8.
|
|
||||||
|
|
||||||
// Now paint the gradient all over our godrays canvas.
|
|
||||||
|
|
||||||
// F[fc](0,0,W,H)
|
|
||||||
|
|
||||||
godraysCtx.fillRect(0, 0, godraysCanvas.width, godraysCanvas.height) |
|
||||||
|
|
||||||
// And set the fillstyle to black, we'll use it to paint our occlusion (mountains).
|
|
||||||
|
|
||||||
// F[fy]='#000'
|
|
||||||
|
|
||||||
godraysCtx.fillStyle = '#000' |
|
||||||
|
|
||||||
// For our 1K demo, we paint our sky a solid #644 reddish-brown. But here - let's do it right.
|
|
||||||
|
|
||||||
// c[fy]=g='#644'
|
|
||||||
// c[fc](0,0,w,h)
|
|
||||||
|
|
||||||
let skyGradient = ctx.createLinearGradient(0, 0, 0, canvas.height) |
|
||||||
skyGradient.addColorStop(0, '#2a3e55') // Blueish at the top.
|
|
||||||
skyGradient.addColorStop(.7, '#8d4835') // Reddish at the bottom.
|
|
||||||
ctx.fillStyle = skyGradient |
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height) |
|
||||||
|
|
||||||
// Our mountains will be made by summing up sine waves of varying frequencies and amplitudes.
|
|
||||||
|
|
||||||
// m=(f,j)=>[1721,947,547,233,73,31,7].reduce((a,v)=>a*j-C(f*v),0)
|
|
||||||
|
|
||||||
function mountainHeight(position, roughness) { |
|
||||||
// Our frequencies (prime numbers to avoid extra repetitions).
|
|
||||||
let frequencies = [1721, 947, 547, 233, 73, 31, 7] |
|
||||||
// Add them up.
|
|
||||||
return frequencies.reduce((height, freq) => height * roughness - Math.cos(freq * position), 0) |
|
||||||
} |
|
||||||
|
|
||||||
// Draw 4 layers of mountains.
|
|
||||||
|
|
||||||
// for(i=0;i<4;i++)for(X=w,c[fy]=`hsl(7,23%,${23-i*6}%`;X--;F[fc](X/4,U/4+1,1,w))c[fc](X,U=W+i*25+m((t+t*i*i)/1e3+X/2e3,i/19-.5)*45,1,w)
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) { |
|
||||||
// Set the main canvas fillStyle to a shade of brown with variable lightness (darker at the front).
|
|
||||||
ctx.fillStyle = `hsl(7, 23%, ${23 - i * 6}%)` |
|
||||||
// For each column in our canvas...
|
|
||||||
for (let x = canvas.width; x--;) { |
|
||||||
// Ok, I don't really remember the details here, basically the (frame+frame*i*i) makes the
|
|
||||||
// near mountains move faster than the far ones. We divide by large numbers because our
|
|
||||||
// mountains repeat at position 1/7*Math.PI*2 or something like that...
|
|
||||||
let mountainPosition = (frame + frame * i * i) / 1000 + x / 2000 |
|
||||||
// Make further mountains more jagged, adds a bit of realism and also makes the godrays
|
|
||||||
// look nicer.
|
|
||||||
let mountainRoughness = i / 19 - .5 |
|
||||||
// 128 is the middle, i * 25 moves the nearer mountains lower on the screen.
|
|
||||||
let y = 128 + i * 25 + mountainHeight(mountainPosition, mountainRoughness) * 45 |
|
||||||
// Paint a 1px-wide rectangle from the mountain's top to below the bottom of the canvas.
|
|
||||||
ctx.fillRect(x, y, 1, 999) // 999 can be any large number...
|
|
||||||
// Paint the same thing in black on the godrays emission canvas, which is 1/4 the size,
|
|
||||||
// and move it one pixel down (otherwise there can be a tiny underlit space between the
|
|
||||||
// mountains and the sky).
|
|
||||||
godraysCtx.fillRect(x / 4, y / 4 + 1, 1, 999) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// The godrays are generated by adding up RGB values, gCt is the bane of all js golfers -
|
|
||||||
// globalCompositeOperation. Set it to 'lighter' on both canvases.
|
|
||||||
|
|
||||||
// c[gCt]=F[gCt]='lighter'
|
|
||||||
|
|
||||||
ctx.globalCompositeOperation = godraysCtx.globalCompositeOperation = 'lighter' |
|
||||||
|
|
||||||
// NOW - let's light this motherfucker up! We'll make several passes over our emission canvas,
|
|
||||||
// each time adding an enlarged copy of it to itself so at the first pass we get 2 copies, then 4,
|
|
||||||
// then 8, then 16 etc... We square our scale factor at each iteration.
|
|
||||||
|
|
||||||
// for(s=1.07;s<5;s*=s)F[da](E,(W-W*s)/2,(H-H*s)/2-T*s+T,W*s,H*s)
|
|
||||||
|
|
||||||
for (let scaleFactor = 1.07; scaleFactor < 5; scaleFactor *= scaleFactor) { |
|
||||||
// The x, y, width and height arguments for drawImage keep the light source (godraysCanvas.width
|
|
||||||
// / 2, godraysCanvas.height / 2 + sunY) in the same spot on the enlarged copy. It basically boils
|
|
||||||
// down to multiplying a 2D matrix by itself. There's probably a better way to do this, but I
|
|
||||||
// couldn't figure it out.
|
|
||||||
godraysCtx.drawImage( |
|
||||||
godraysCanvas, |
|
||||||
(godraysCanvas.width - godraysCanvas.width * scaleFactor) / 2, |
|
||||||
(godraysCanvas.height - godraysCanvas.height * scaleFactor) / 2 - sunY * scaleFactor + sunY, |
|
||||||
godraysCanvas.width * scaleFactor, |
|
||||||
godraysCanvas.height * scaleFactor |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
// Now that our godrays are rendered, draw them to our output canvas (whose globalCompositeOperation
|
|
||||||
// is already set to 'lighter').
|
|
||||||
|
|
||||||
// c[da](E,0,0,w,h)
|
|
||||||
|
|
||||||
ctx.drawImage(godraysCanvas, 0, 0, canvas.width, canvas.height) |
|
||||||
|
|
||||||
// All done.
|
|
||||||
|
|
||||||
// this[rte](B)}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(onFrame) |
|
||||||
} |
|
||||||
|
|
||||||
// Call our requestAnimationFrame handler to start rendering. Since it takes no arguments use the argument
|
|
||||||
// list to create our godrays canvas with cloneNode, which also takes no arguments... use it to setup a
|
|
||||||
// Math.cos shortcut (we'll skip this in our longform version).
|
|
||||||
|
|
||||||
// B(E=a.cloneNode(C=Math.cos))
|
|
||||||
|
|
||||||
let godraysCanvas = canvas.cloneNode() |
|
||||||
onFrame() |
|
||||||
|
|
||||||
// Phew... that took a while, but we're finally done with the visuals. Now for the audio part -
|
|
||||||
//
|
|
||||||
// The synthesizer is based on the Karplus-Strong algorithm which uses a very short delay loop as a
|
|
||||||
// resonator. I was initially aiming for a realistic string quartet but time and space constraints
|
|
||||||
// have forced me to massively compromise.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// The music is a 64-note melody that ends up an octave above where it started, spread out in a 4-voice
|
|
||||||
// canon. We pre-render a single voice and then add up 4 in our ScriptProcessor callback.
|
|
||||||
|
|
||||||
// Big hairy render loop, let's break it to pieces and explain...
|
|
||||||
|
|
||||||
// for(M=[Y=[V=J=I=i=0]];i<h;i++)for(j=2e4;j--;T=Y[I|0]=M[J++]=O%9)O=Math.random()-.5+T/5+Y[(I=++I%(7e3/2**(("!!----,*,(444420/20/-0/---,,--//((4444202/;;;;986986420/00--//,,".charCodeAt(i&63)+12*(i>>6))/12)))|0]*.8||0
|
|
||||||
|
|
||||||
let encodedMelody = "!!----,*,(444420/20/-0/---,,--//((4444202/;;;;986986420/00--//,," |
|
||||||
|
|
||||||
// M=[Y=[V=J=I=i=0]]
|
|
||||||
let voiceBuffer = [] // M = [...]
|
|
||||||
let ksDelayBuffer = [] // Y = [...]
|
|
||||||
let sampleOffset = 0 // V = 0 (used later)
|
|
||||||
let J = 0 // What the hell is J????
|
|
||||||
|
|
||||||
// Oh fuck it. It's 4am and I have no idea how this thing works. Maybe I'll write it up later.
|
|
||||||
// Besides, you just came here for the godrays, right?
|
|
||||||
|
|
||||||
// A(G=new AudioContext)
|
|
||||||
// A(S=G[cSr](w*8,0,1))
|
|
||||||
// S[oo]=e=>{A(e);A(o=e[oB]);for(i=0;i<w*8;o[gn](0)[i++]=O/32,V++)for(O=0,K=4;K--;O+=T>0&&M[T%J])T=V-(K/32*9)*J}
|
|
||||||
// S.connect(G[da])
|
|
||||||
} |
|
||||||
@ -1,30 +0,0 @@ |
|||||||
export default class Particle { |
|
||||||
constructor() { |
|
||||||
this.angleX = Math.random() * Math.PI * 2 |
|
||||||
this.angleY = Math.random() * Math.PI * 2 |
|
||||||
this.speedX = Math.random() |
|
||||||
this.speedY = Math.random() |
|
||||||
this.radius = 500 |
|
||||||
} |
|
||||||
|
|
||||||
update({ctx, width, height, gravityDistance, mousePos}, speed = 0.035) { |
|
||||||
let x = Math.cos(this.angleX) * this.radius, |
|
||||||
y = Math.sin(this.angleY) * this.radius, |
|
||||||
currentSpeedX = this.speedX * speed, |
|
||||||
currentSpeedY = this.speedY * speed |
|
||||||
|
|
||||||
if (Math.abs(mousePos.x - width / 2 - x) < gravityDistance && Math.abs(mousePos.y - height / 2 - y) < gravityDistance) { |
|
||||||
currentSpeedX = currentSpeedX * (Math.abs(mousePos.x - width / 2 - x) / gravityDistance) * 0.5 |
|
||||||
currentSpeedY = currentSpeedY * (Math.abs(mousePos.y - height / 2 - y) / gravityDistance) * 0.5 |
|
||||||
} |
|
||||||
|
|
||||||
this.angleX += currentSpeedX |
|
||||||
this.angleY += currentSpeedY |
|
||||||
|
|
||||||
ctx.beginPath() |
|
||||||
ctx.fillStyle = "white" |
|
||||||
|
|
||||||
ctx.arc(width / 2 + x, height / 2 + y, 2.5, 0, Math.PI * 2, false) |
|
||||||
ctx.fill() |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,71 +0,0 @@ |
|||||||
import Particle from "./Particle" |
|
||||||
|
|
||||||
export default class MoveFollowMouse { |
|
||||||
constructor(canvas, {num = 10, gravityDistance = 100} = {}) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext('2d') |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.particles = [] |
|
||||||
this.num = num |
|
||||||
this.gravityDistance = gravityDistance |
|
||||||
this.mousePos = {x: 0, y: 0} |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.getMousePos = this.getMousePos.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
for (let i = 0; i < this.num; i += 1) { |
|
||||||
this.particles.push(new Particle()) |
|
||||||
} |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
window.addEventListener('mousemove', this.getMousePos) |
|
||||||
this.resize() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
window.removeEventListener('mousemove', this.getMousePos) |
|
||||||
this.stopSign = true |
|
||||||
this.particles = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.clearRect(0, 0, this.width, this.height) |
|
||||||
for (let i = 0; i < this.num; i += 1) { |
|
||||||
this.particles[i].update({ |
|
||||||
ctx: this.ctx, |
|
||||||
width: this.width, |
|
||||||
height: this.height, |
|
||||||
gravityDistance: this.gravityDistance, |
|
||||||
mousePos: this.mousePos |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
getMousePos(evt) { |
|
||||||
const rect = this.canvas.getBoundingClientRect() |
|
||||||
this.mousePos = {x: evt.clientX - rect.left, y: evt.clientY - rect.top} |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.width = this.canvas.width = window.innerWidth |
|
||||||
this.height = this.canvas.height = window.innerHeight |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,90 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
import util from "./util" |
|
||||||
|
|
||||||
export default class Particle { |
|
||||||
constructor({add, sphereRad, fLen, dotImageList}) { |
|
||||||
this.distPos = { |
|
||||||
theta: add === true ? Math.PI / 2 : Math.random() * Math.PI, |
|
||||||
phi: 2 * Math.random() * Math.PI |
|
||||||
} |
|
||||||
|
|
||||||
this.distPos.x = sphereRad * Math.sin(this.distPos.theta) * Math.cos(this.distPos.phi) |
|
||||||
this.distPos.y = sphereRad * Math.sin(this.distPos.theta) * Math.sin(this.distPos.phi) |
|
||||||
this.distPos.z = sphereRad * Math.cos(this.distPos.theta) |
|
||||||
this.distVec = new Vector(this.distPos.x, this.distPos.y, this.distPos.z) |
|
||||||
this.unitVec = this.distVec.unit() |
|
||||||
|
|
||||||
this.startVec = this.distVec.multiply(1 + Math.random() * 2) |
|
||||||
this.x = this.startVec.x |
|
||||||
this.y = this.startVec.y |
|
||||||
this.z = this.startVec.z |
|
||||||
|
|
||||||
this.veloRate = 1 + Math.random() |
|
||||||
this.velo = this.unitVec.negative().multiply(this.veloRate) |
|
||||||
this.finalVelo = 0 |
|
||||||
this.m = fLen / (fLen - this.z) |
|
||||||
|
|
||||||
this.age = this.life = 50 + Math.floor(Math.random() * 500) |
|
||||||
this.turnAngle = 0 |
|
||||||
this.wanderTime = 200 |
|
||||||
this.radius = 1 + Math.random() * 3 |
|
||||||
|
|
||||||
let colorRandom = Math.floor(Math.random() * 4) |
|
||||||
switch (colorRandom) { |
|
||||||
case 0: |
|
||||||
this.img = dotImageList[0] |
|
||||||
this.color = "rgba(70,255,140," |
|
||||||
break |
|
||||||
case 1: |
|
||||||
this.img = dotImageList[1] |
|
||||||
this.color = "rgba(90,90,90," |
|
||||||
break |
|
||||||
case 2: |
|
||||||
this.img = dotImageList[2] |
|
||||||
break |
|
||||||
case 3: |
|
||||||
this.img = dotImageList[3] |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
update({sphereRad, fLen, turnSpeed}) { |
|
||||||
this.nowPos = new Vector(this.x, this.y, this.z) |
|
||||||
this.nowPosUnit = this.nowPos.unit() |
|
||||||
|
|
||||||
if (this.wanderTime > 0 && this.nowPos.length() > (sphereRad * 1.2)) { |
|
||||||
this.wanderTime-- |
|
||||||
this.velo.x += 0.1 * (Math.random() * 2 - 1) |
|
||||||
this.velo.y += 0.1 * (Math.random() * 2 - 1) |
|
||||||
this.velo.z += 0.1 * (Math.random() * 2 - 1) |
|
||||||
this.x = this.x + this.velo.x |
|
||||||
this.y = this.y + this.velo.y |
|
||||||
this.z = this.z + this.velo.z |
|
||||||
this.op = util.map(this.nowPos.length(), sphereRad, this.startVec.length(), 1, 0) |
|
||||||
} |
|
||||||
else if (this.nowPos.length() > sphereRad) { |
|
||||||
if (this.finalPos === 0) { |
|
||||||
this.finalPos = this.nowPosUnit.multiply(sphereRad) |
|
||||||
} |
|
||||||
if (this.finalVelo === 0) { |
|
||||||
this.finalVelo = this.nowPosUnit.negative().multiply(this.veloRate) |
|
||||||
} |
|
||||||
this.x = this.x + this.finalVelo.x |
|
||||||
this.y = this.y + this.finalVelo.y |
|
||||||
this.z = this.z + this.finalVelo.z |
|
||||||
this.op = util.map(this.nowPos.length(), sphereRad, this.startVec.length(), 1, 0) |
|
||||||
} |
|
||||||
else { |
|
||||||
this.op = this.life / (this.age / 2) |
|
||||||
this.turnAngle = (this.turnAngle + turnSpeed) % (Math.PI * 2) |
|
||||||
let cosAngle = Math.cos(turnSpeed) |
|
||||||
let sinAngle = Math.sin(turnSpeed) |
|
||||||
this.x = cosAngle * this.nowPos.x + sinAngle * this.nowPos.z |
|
||||||
this.z = -sinAngle * this.nowPos.x + cosAngle * this.nowPos.z |
|
||||||
this.y = this.nowPos.y |
|
||||||
this.life-- |
|
||||||
} |
|
||||||
|
|
||||||
this.m = fLen / (fLen - this.z) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,74 +0,0 @@ |
|||||||
export default class Vector { |
|
||||||
constructor(x, y, z) { |
|
||||||
this.x = x || 0 |
|
||||||
this.y = y || 0 |
|
||||||
this.z = z || 0 |
|
||||||
} |
|
||||||
|
|
||||||
negative() { |
|
||||||
return new Vector(-this.x, -this.y, -this.z) |
|
||||||
} |
|
||||||
|
|
||||||
add(v) { |
|
||||||
if (v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z) |
|
||||||
else return new Vector(this.x + v, this.y + v, this.z + v) |
|
||||||
} |
|
||||||
|
|
||||||
subtract(v) { |
|
||||||
if (v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z) |
|
||||||
else return new Vector(this.x - v, this.y - v, this.z - v) |
|
||||||
} |
|
||||||
|
|
||||||
multiply(v) { |
|
||||||
if (v instanceof Vector) return new Vector(this.x * v.x, this.y * v.y, this.z * v.z) |
|
||||||
else return new Vector(this.x * v, this.y * v, this.z * v) |
|
||||||
} |
|
||||||
|
|
||||||
divide(v) { |
|
||||||
if (v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z) |
|
||||||
else return new Vector(this.x / v, this.y / v, this.z / v) |
|
||||||
} |
|
||||||
|
|
||||||
dot(v) { |
|
||||||
return this.x * v.x + this.y * v.y + this.z * v.z |
|
||||||
} |
|
||||||
|
|
||||||
cross(v) { |
|
||||||
return new Vector( |
|
||||||
this.y * v.z - this.z * v.y, |
|
||||||
this.z * v.x - this.x * v.z, |
|
||||||
this.x * v.y - this.y * v.x |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
length() { |
|
||||||
return Math.sqrt(this.dot(this)) |
|
||||||
} |
|
||||||
|
|
||||||
unit() { |
|
||||||
return this.divide(this.length()) |
|
||||||
} |
|
||||||
|
|
||||||
min() { |
|
||||||
return Math.min(Math.min(this.x, this.y), this.z) |
|
||||||
} |
|
||||||
|
|
||||||
max() { |
|
||||||
return Math.max(Math.max(this.x, this.y), this.z) |
|
||||||
} |
|
||||||
|
|
||||||
angleTo(a) { |
|
||||||
return Math.acos(this.dot(a) / (this.length() * a.length())) |
|
||||||
} |
|
||||||
|
|
||||||
clone() { |
|
||||||
return new Vector(this.x, this.y, this.z) |
|
||||||
} |
|
||||||
|
|
||||||
init(x, y, z) { |
|
||||||
this.x = x |
|
||||||
this.y = y |
|
||||||
this.z = z |
|
||||||
return this |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,91 +0,0 @@ |
|||||||
import Particle from "./Particle" |
|
||||||
|
|
||||||
export default class ParticleBall { |
|
||||||
constructor(canvas, {sphereRad = 130, fLen = 300, maxParticle = 400, turnSpeed = 0.005} = {}) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext("2d") |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.sphereRad = sphereRad |
|
||||||
this.fLen = fLen |
|
||||||
this.maxParticle = maxParticle |
|
||||||
this.turnSpeed = turnSpeed |
|
||||||
this.particles = [] |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
this.resize() |
|
||||||
this.initDot() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
this.stopSign = true |
|
||||||
this.particles = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.clearRect(0, 0, this.width, this.height) |
|
||||||
if (this.particles.length < this.maxParticle) { |
|
||||||
this.particles.push(new Particle({ |
|
||||||
sphereRad: this.sphereRad, |
|
||||||
fLen: this.fLen, |
|
||||||
dotImageList: this.dotImageList |
|
||||||
})) |
|
||||||
} |
|
||||||
for (let i = 0; i < this.particles.length; i++) { |
|
||||||
let p = this.particles[i] |
|
||||||
if (p.life === 0) { |
|
||||||
this.particles.splice(i, 1) |
|
||||||
this.particles.push(new Particle({ |
|
||||||
sphereRad: this.sphereRad, |
|
||||||
fLen: this.fLen, |
|
||||||
dotImageList: this.dotImageList |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
p.update({sphereRad: this.sphereRad, fLen: this.fLen, turnSpeed: this.turnSpeed}) |
|
||||||
|
|
||||||
if (p.m > 0) { |
|
||||||
this.ctx.save() |
|
||||||
this.ctx.globalAlpha = p.op |
|
||||||
this.ctx.drawImage(p.img, p.x * p.m + this.CenterX, this.CenterY - p.y * p.m, p.radius * p.m * 2, p.radius * p.m * 2) |
|
||||||
this.ctx.restore() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.width = this.canvas.width = window.innerWidth |
|
||||||
this.height = this.canvas.height = window.innerHeight |
|
||||||
this.CenterX = this.width / 2 |
|
||||||
this.CenterY = this.height / 2 |
|
||||||
} |
|
||||||
|
|
||||||
initDot() { |
|
||||||
this.dotImageList = [] |
|
||||||
for (let i = 1; i <= 4; i++) { |
|
||||||
const dotImage = new Image() |
|
||||||
dotImage.src = `${process.env.BASE_URL}static/img/dot${i}.png` |
|
||||||
this.dotImageList.push(dotImage) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
const util = { |
|
||||||
norm: function (value, min, max) { |
|
||||||
return (value - min) / (max - min) |
|
||||||
}, |
|
||||||
lerp: function (norm, min, max) { |
|
||||||
return min + norm * (max - min) |
|
||||||
}, |
|
||||||
map: function (value, sourceMin, sourceMax, destMin, destMax) { |
|
||||||
return this.lerp(this.norm(value, sourceMin, sourceMax), destMin, destMax) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default util |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
function getLimitedRandom(min, max, roundToInteger) { |
|
||||||
let number = Math.random() * (max - min) + min |
|
||||||
if (roundToInteger) number = Math.round(number) |
|
||||||
return number |
|
||||||
} |
|
||||||
|
|
||||||
function returnRandomArrayItem(array) { |
|
||||||
return array[Math.floor(Math.random() * array.length)] |
|
||||||
} |
|
||||||
|
|
||||||
export default class Particle { |
|
||||||
constructor(parent, x, y) { |
|
||||||
this.network = parent |
|
||||||
this.canvas = parent.canvas |
|
||||||
this.ctx = parent.ctx |
|
||||||
this.particleColor = returnRandomArrayItem(this.network.options.particleColors) |
|
||||||
this.radius = getLimitedRandom(1.5, 2.5) |
|
||||||
this.opacity = 0 |
|
||||||
this.x = x || Math.random() * this.canvas.width |
|
||||||
this.y = y || Math.random() * this.canvas.height |
|
||||||
this.velocity = { |
|
||||||
x: (Math.random() - 0.5) * parent.options.velocity, |
|
||||||
y: (Math.random() - 0.5) * parent.options.velocity |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
update() { |
|
||||||
this.opacity = this.opacity < 1 ? this.opacity + 0.01 : 1 |
|
||||||
|
|
||||||
// Change dir if outside map
|
|
||||||
if (this.x > this.canvas.width + 100 || this.x < -100) { |
|
||||||
this.velocity.x = -this.velocity.x |
|
||||||
} |
|
||||||
if (this.y > this.canvas.height + 100 || this.y < -100) { |
|
||||||
this.velocity.y = -this.velocity.y |
|
||||||
} |
|
||||||
|
|
||||||
// Update position
|
|
||||||
this.x += this.velocity.x |
|
||||||
this.y += this.velocity.y |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.beginPath() |
|
||||||
this.ctx.fillStyle = this.particleColor |
|
||||||
this.ctx.globalAlpha = this.opacity |
|
||||||
this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI) |
|
||||||
this.ctx.fill() |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,207 +0,0 @@ |
|||||||
import Particle from "./Particle" |
|
||||||
|
|
||||||
export default class ParticleNetwork { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext("2d") |
|
||||||
this.options = { |
|
||||||
velocity: 1, // the higher the faster
|
|
||||||
density: 15000, // the lower the denser
|
|
||||||
netLineDistance: 200, |
|
||||||
netLineColor: '#929292', |
|
||||||
particleColors: ['#aaa'] // ['#6D4E5C', '#aaa', '#FFC458' ]
|
|
||||||
} |
|
||||||
this.rAF = null |
|
||||||
this.timer = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
this.createParticles(true) |
|
||||||
this.bindUiActions() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
this.unbindUiActions() |
|
||||||
this.stopSign = true |
|
||||||
this.particles = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
this.ctx.globalAlpha = 1 |
|
||||||
|
|
||||||
// Draw connections
|
|
||||||
for (let i = 0; i < this.particles.length; i++) { |
|
||||||
for (let j = this.particles.length - 1; j > i; j--) { |
|
||||||
let distance, p1 = this.particles[i], p2 = this.particles[j] |
|
||||||
|
|
||||||
// check very simply if the two points are even a candidate for further measurements
|
|
||||||
distance = Math.min(Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y)) |
|
||||||
if (distance > this.options.netLineDistance) continue |
|
||||||
|
|
||||||
// the two points seem close enough, now let's measure precisely
|
|
||||||
distance = Math.sqrt( |
|
||||||
Math.pow(p1.x - p2.x, 2) + |
|
||||||
Math.pow(p1.y - p2.y, 2) |
|
||||||
) |
|
||||||
if (distance > this.options.netLineDistance) continue |
|
||||||
|
|
||||||
this.ctx.beginPath() |
|
||||||
this.ctx.strokeStyle = this.options.netLineColor |
|
||||||
this.ctx.globalAlpha = (this.options.netLineDistance - distance) / this.options.netLineDistance * p1.opacity * p2.opacity |
|
||||||
this.ctx.lineWidth = 0.7 |
|
||||||
this.ctx.moveTo(p1.x, p1.y) |
|
||||||
this.ctx.lineTo(p2.x, p2.y) |
|
||||||
this.ctx.stroke() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Draw particles
|
|
||||||
for (let i = 0; i < this.particles.length; i++) { |
|
||||||
this.particles[i].update() |
|
||||||
this.particles[i].draw() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
createParticles(isInitial) { |
|
||||||
// Initialise / reset particles
|
|
||||||
this.particles = [] |
|
||||||
let quantity = this.canvas.width * this.canvas.height / this.options.density |
|
||||||
|
|
||||||
if (isInitial) { |
|
||||||
let counter = 0 |
|
||||||
window.clearInterval(this.timer) |
|
||||||
this.timer = window.setInterval(() => { |
|
||||||
if (counter < quantity - 1) this.particles.push(new Particle(this)) |
|
||||||
else window.clearInterval(this.timer) |
|
||||||
counter++ |
|
||||||
}, 250) |
|
||||||
} |
|
||||||
else for (let i = 0; i < quantity; i++) this.particles.push(new Particle(this)) |
|
||||||
} |
|
||||||
|
|
||||||
createInteractionParticle() { |
|
||||||
this.interactionParticle = new Particle(this) |
|
||||||
this.interactionParticle.velocity = {x: 0, y: 0} |
|
||||||
this.particles.push(this.interactionParticle) |
|
||||||
return this.interactionParticle |
|
||||||
} |
|
||||||
|
|
||||||
removeInteractionParticle() { |
|
||||||
let index = this.particles.indexOf(this.interactionParticle) |
|
||||||
if (index > -1) { |
|
||||||
this.interactionParticle = undefined |
|
||||||
this.particles.splice(index, 1) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bindUiActions() { |
|
||||||
this.spawnQuantity = 3 |
|
||||||
this.mouseIsDown = false |
|
||||||
this.touchIsMoving = false |
|
||||||
|
|
||||||
this.onMouseMove = function (e) { |
|
||||||
!this.interactionParticle && this.createInteractionParticle() |
|
||||||
this.interactionParticle.x = e.offsetX |
|
||||||
this.interactionParticle.y = e.offsetY |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onTouchMove = function (e) { |
|
||||||
e.preventDefault() |
|
||||||
this.touchIsMoving = true |
|
||||||
!this.interactionParticle && this.createInteractionParticle() |
|
||||||
this.interactionParticle.x = e.changedTouches[0].clientX |
|
||||||
this.interactionParticle.y = e.changedTouches[0].clientY |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onMouseDown = function (e) { |
|
||||||
this.mouseIsDown = true |
|
||||||
let counter = 0 |
|
||||||
let quantity = this.spawnQuantity |
|
||||||
let intervalId = window.setInterval(function () { |
|
||||||
if (this.mouseIsDown) { |
|
||||||
if (counter === 1) quantity = 1 |
|
||||||
for (let i = 0; i < quantity; i++) { |
|
||||||
if (this.interactionParticle) { |
|
||||||
this.particles.push(new Particle(this, this.interactionParticle.x, this.interactionParticle.y)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else window.clearInterval(intervalId) |
|
||||||
counter++ |
|
||||||
}.bind(this), 50) |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onTouchStart = function (e) { |
|
||||||
e.preventDefault() |
|
||||||
window.setTimeout(function () { |
|
||||||
if (!this.touchIsMoving) { |
|
||||||
for (let i = 0; i < this.spawnQuantity; i++) { |
|
||||||
this.particles.push(new Particle(this, e.changedTouches[0].clientX, e.changedTouches[0].clientY)) |
|
||||||
} |
|
||||||
} |
|
||||||
}.bind(this), 200) |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onMouseUp = function (e) { |
|
||||||
this.mouseIsDown = false |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onMouseOut = function (e) { |
|
||||||
this.removeInteractionParticle() |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.onTouchEnd = function (e) { |
|
||||||
e.preventDefault() |
|
||||||
this.touchIsMoving = false |
|
||||||
this.removeInteractionParticle() |
|
||||||
}.bind(this) |
|
||||||
|
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
window.addEventListener('mousemove', this.onMouseMove) |
|
||||||
window.addEventListener('touchmove', this.onTouchMove) |
|
||||||
window.addEventListener('mousedown', this.onMouseDown) |
|
||||||
window.addEventListener('touchstart', this.onTouchStart) |
|
||||||
window.addEventListener('mouseup', this.onMouseUp) |
|
||||||
window.addEventListener('mouseout', this.onMouseOut) |
|
||||||
window.addEventListener('touchend', this.onTouchEnd) |
|
||||||
} |
|
||||||
|
|
||||||
unbindUiActions() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
window.removeEventListener('mousemove', this.onMouseMove) |
|
||||||
window.removeEventListener('touchmove', this.onTouchMove) |
|
||||||
window.removeEventListener('mousedown', this.onMouseDown) |
|
||||||
window.removeEventListener('touchstart', this.onTouchStart) |
|
||||||
window.removeEventListener('mouseup', this.onMouseUp) |
|
||||||
window.removeEventListener('mouseout', this.onMouseOut) |
|
||||||
window.removeEventListener('touchend', this.onTouchEnd) |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
this.createParticles() |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,160 +0,0 @@ |
|||||||
function colorIntToHexString(color) { |
|
||||||
let s = color.toString(16) |
|
||||||
return '0'.repeat(6 - s.length) + s |
|
||||||
} |
|
||||||
|
|
||||||
export default class ParticleWave { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext("2d") |
|
||||||
this.config = { |
|
||||||
colors: { |
|
||||||
background: 0x000000, |
|
||||||
particle: 0x477cc2 |
|
||||||
}, |
|
||||||
alpha: { |
|
||||||
particle: 1 |
|
||||||
}, |
|
||||||
particleCount: 10000 |
|
||||||
} |
|
||||||
this.particleWaveWalker = 0 |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
initParticle() { |
|
||||||
this.particles = new Float32Array(this.config.particleCount * 2) |
|
||||||
for (let i = 0; i < this.particles.length; i += 2) { |
|
||||||
this.particles[i] = Math.random() |
|
||||||
this.particles[i + 1] = Math.random() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
initParticleColor() { |
|
||||||
this.particleColorRGB = new Float32Array(3) |
|
||||||
this.particleColorRGB[0] = this.config.colors.particle >> 16 & 0xff |
|
||||||
this.particleColorRGB[1] = this.config.colors.particle >> 8 & 0xff |
|
||||||
this.particleColorRGB[2] = this.config.colors.particle & 0xff |
|
||||||
this.particleFillStyle = 'rgb(' + this.particleColorRGB[0] + ',' + this.particleColorRGB[1] + ',' + this.particleColorRGB[2] + ')' |
|
||||||
} |
|
||||||
|
|
||||||
initSmoothGradient() { |
|
||||||
this.smoothGradient = this.ctx.createLinearGradient( |
|
||||||
this.canvas.width / 2, |
|
||||||
0, |
|
||||||
this.canvas.width / 2, |
|
||||||
this.canvas.height |
|
||||||
) |
|
||||||
this.smoothGradient.addColorStop(0.25, 'rgba(0, 0, 0, 0)') |
|
||||||
this.smoothGradient.addColorStop(0.45, 'rgba(0, 0, 0, 0.9)') |
|
||||||
this.smoothGradient.addColorStop(0.5, 'rgba(0, 0, 0, 1)') |
|
||||||
this.smoothGradient.addColorStop(0.55, 'rgba(0, 0, 0, 0.9)') |
|
||||||
this.smoothGradient.addColorStop(0.75, 'rgba(0, 0, 0, 0)') |
|
||||||
} |
|
||||||
|
|
||||||
initWaterGradient() { |
|
||||||
this.waterGradient = this.ctx.createLinearGradient( |
|
||||||
this.canvas.width / 2, |
|
||||||
this.canvas.height / 2, |
|
||||||
this.canvas.width / 2, |
|
||||||
this.canvas.height |
|
||||||
) |
|
||||||
this.waterGradient.addColorStop(0, 'rgba(0, 0, 30, 0)') |
|
||||||
this.waterGradient.addColorStop(1, 'rgba(30, 0, 60, 0.5)') |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
this.resize() |
|
||||||
this.initParticle() |
|
||||||
this.initParticleColor() |
|
||||||
this.initSmoothGradient() |
|
||||||
this.initWaterGradient() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
this.stopSign = true |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.fillStyle = '#' + colorIntToHexString(this.config.colors.background) |
|
||||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
this.ctx.fillStyle = this.waterGradient |
|
||||||
this.ctx.fillRect(0, this.canvas.height / 2, this.canvas.width, this.canvas.height / 2) |
|
||||||
|
|
||||||
this.renderParticle() |
|
||||||
|
|
||||||
this.ctx.fillStyle = this.particleFillStyle |
|
||||||
this.ctx.fillStyle = this.smoothGradient |
|
||||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
renderParticle() { |
|
||||||
this.particleWaveWalker += 0.03 |
|
||||||
this.ctx.fillStyle = this.particleFillStyle |
|
||||||
|
|
||||||
let radius = {min: 1, add: 5}, |
|
||||||
midY = this.canvas.height / 2, |
|
||||||
midX = this.canvas.width / 2, |
|
||||||
spreadX = 5, |
|
||||||
spreadZ = 0.0, |
|
||||||
modZ = 0.0, |
|
||||||
addX = 0, |
|
||||||
addY = 0, |
|
||||||
p = {x: 0.0, y: 0.0, r: 0.0}, |
|
||||||
waveControl = 10 |
|
||||||
|
|
||||||
for (let i = 0, xIndex, zIndex; i < this.particles.length; i += 2) { |
|
||||||
xIndex = i |
|
||||||
zIndex = i + 1 |
|
||||||
this.particles[zIndex] += 0.003 |
|
||||||
if (this.particles[zIndex] > 1) { |
|
||||||
this.particles[zIndex] = 0 |
|
||||||
this.particles[xIndex] = Math.random() |
|
||||||
} |
|
||||||
|
|
||||||
if (this.particles[zIndex] < 0.3) continue |
|
||||||
|
|
||||||
modZ = Math.pow(this.particles[zIndex], 2) |
|
||||||
spreadZ = 1 + (spreadX - 1) * modZ |
|
||||||
addX = (0.5 - this.particles[xIndex]) * this.canvas.width * spreadZ |
|
||||||
addY = midY * modZ * (1 + 3 / waveControl) |
|
||||||
|
|
||||||
p.x = midX + addX |
|
||||||
p.y = midY + addY |
|
||||||
p.r = radius.min + modZ * radius.add |
|
||||||
p.y += Math.sin(this.particles[xIndex] * 50 + this.particleWaveWalker) * addY / waveControl |
|
||||||
p.y += Math.cos(this.particles[zIndex] * 10 + this.particleWaveWalker) * addY / waveControl |
|
||||||
p.y -= Math.cos(this.particles[zIndex] + this.particles[xIndex] * 10 + this.particleWaveWalker) * addY / waveControl |
|
||||||
p.y -= Math.cos(this.particles[xIndex] * 50 + this.particleWaveWalker) * addY / waveControl |
|
||||||
p.y -= Math.sin(this.particles[zIndex] * 10 + this.particleWaveWalker) * addY / waveControl |
|
||||||
|
|
||||||
if (p.x < 0 || p.x > this.canvas.width) continue |
|
||||||
|
|
||||||
this.ctx.fillRect(p.x, p.y, p.r, p.r) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,29 +0,0 @@ |
|||||||
import Vector from "./Vector" |
|
||||||
|
|
||||||
export default class RainDrop { |
|
||||||
constructor(parent) { |
|
||||||
this.size = 2 |
|
||||||
this.parent = parent |
|
||||||
this.init() |
|
||||||
} |
|
||||||
|
|
||||||
init() { |
|
||||||
this.life = 0 |
|
||||||
this.ttl = Math.random() * 500 + 300 |
|
||||||
this.position = new Vector(Math.random() * window.innerWidth, 0) |
|
||||||
this.velocity = new Vector(0.5 - Math.random(), Math.random() + 0.2) |
|
||||||
} |
|
||||||
|
|
||||||
update() { |
|
||||||
if (this.position.x > window.innerWidth || this.position.x < -this.size || this.life > this.ttl) { |
|
||||||
this.init() |
|
||||||
} |
|
||||||
if (this.position.y > this.parent.floor) { |
|
||||||
this.position.y = this.parent.floor - this.size |
|
||||||
this.velocity.y *= -0.2 - Math.random() * 0.3 |
|
||||||
} |
|
||||||
this.life++ |
|
||||||
this.position.add(this.velocity) |
|
||||||
this.velocity.y += 0.1 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
export default class Vector { |
|
||||||
constructor(x, y) { |
|
||||||
this.x = x || 0 |
|
||||||
this.y = y || 0 |
|
||||||
} |
|
||||||
|
|
||||||
add({x, y}) { |
|
||||||
this.x += x |
|
||||||
this.y += y |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,85 +0,0 @@ |
|||||||
import RainDrop from "./RainDrop" |
|
||||||
|
|
||||||
export default class ReflectRain { |
|
||||||
constructor(canvas, {rainDropCount = 500, rainColor = 'rgba(150,180,255,0.8)', backgroundColor = '#2d3a4b'} = {}) { |
|
||||||
this.props = {rainDropCount, rainColor, backgroundColor} |
|
||||||
this.rainDrops = [] |
|
||||||
this.timer = null |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext('2d') |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
this.resize() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
this.stopSign = true |
|
||||||
this.rainDrops = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
this.timer && window.clearTimeout(this.timer) |
|
||||||
return |
|
||||||
} |
|
||||||
if (this.rainDrops.length < this.props.rainDropCount) { |
|
||||||
this.timer = window.setTimeout(() => this.rainDrops.push(new RainDrop(this)), Math.random() * 200) |
|
||||||
} |
|
||||||
else if (this.timer) { |
|
||||||
window.clearTimeout(this.timer) |
|
||||||
this.timer = null |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.fillStyle = this.props.backgroundColor |
|
||||||
this.ctx.fillRect(0, 0, this.dimensions.width, this.floor) |
|
||||||
for (let i = 0, len = this.rainDrops.length; i < len; i++) { |
|
||||||
let rainDrop = this.rainDrops[i] |
|
||||||
rainDrop.update() |
|
||||||
this.ctx.fillStyle = this.props.rainColor |
|
||||||
this.ctx.fillRect(rainDrop.position.x, rainDrop.position.y, rainDrop.size, rainDrop.size) |
|
||||||
} |
|
||||||
this.reflect() |
|
||||||
} |
|
||||||
|
|
||||||
reflect() { |
|
||||||
let grad = this.ctx.createLinearGradient(this.dimensions.width / 2, this.floor * 0.6, this.dimensions.width / 2, this.floor) |
|
||||||
grad.addColorStop(0, 'rgba(80,90,100,1)') |
|
||||||
grad.addColorStop(1, 'rgba(80,90,100,0)') |
|
||||||
this.ctx.save() |
|
||||||
this.ctx.scale(1, -1) |
|
||||||
this.ctx.translate(0, this.floor * -2) |
|
||||||
this.ctx.filter = 'blur(3px) saturate(150%)' |
|
||||||
this.ctx.drawImage(this.canvas, 0, 0, this.dimensions.width, this.floor, 0, 0, this.dimensions.width, this.floor) |
|
||||||
this.ctx.fillStyle = grad |
|
||||||
this.ctx.fillRect(0, 0, this.dimensions.width, this.floor) |
|
||||||
this.ctx.restore() |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.dimensions = { |
|
||||||
width: window.innerWidth, |
|
||||||
height: window.innerHeight |
|
||||||
} |
|
||||||
this.canvas.width = this.dimensions.width |
|
||||||
this.canvas.height = this.dimensions.height |
|
||||||
this.floor = this.dimensions.height * 0.7 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,18 +0,0 @@ |
|||||||
import {rand} from "./util" |
|
||||||
|
|
||||||
export default class Spark { |
|
||||||
constructor(x, y, options) { |
|
||||||
this.x = x |
|
||||||
this.y = y |
|
||||||
this.age = 0 |
|
||||||
this.acceleration = rand(options.acceleration[0], options.acceleration[1]) |
|
||||||
this.color = options.randColor ? rand(0, 255) + "," + rand(0, 255) + "," + rand(0, 255) : OPT.color |
|
||||||
this.opacity = options.maxOpacity - this.age / (options.lifetime * rand(1, 10)) |
|
||||||
} |
|
||||||
|
|
||||||
go(options) { |
|
||||||
this.x += options.speed * options.direction.x * this.acceleration / 2 |
|
||||||
this.y += options.speed * options.direction.y * this.acceleration / 2 |
|
||||||
this.opacity = options.maxOpacity - ++this.age / options.lifetime |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,89 +0,0 @@ |
|||||||
import Spark from "./Spark" |
|
||||||
import {rand} from "./util" |
|
||||||
|
|
||||||
export default class SparkRain { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext("2d") |
|
||||||
this.options = { |
|
||||||
amount: 10000, |
|
||||||
speed: 0.1, // pixels per frame
|
|
||||||
lifetime: 500, |
|
||||||
direction: {x: -0.5, y: 1}, |
|
||||||
size: [1, 1], |
|
||||||
maxOpacity: 1, |
|
||||||
color: "150, 150, 150", |
|
||||||
randColor: true, |
|
||||||
acceleration: [5, 40] |
|
||||||
} |
|
||||||
if (window.innerWidth < 520) { |
|
||||||
this.options.speed = 0.05 |
|
||||||
this.options.color = "150, 150, 150" |
|
||||||
} |
|
||||||
this.sparks = [] |
|
||||||
this.rAF = null |
|
||||||
this.timer = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.addSpark = this.addSpark.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
this.resize() |
|
||||||
this.timer = window.setInterval( |
|
||||||
() => this.sparks.length < this.options.amount && this.addSpark(), |
|
||||||
1000 / this.options.amount) |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
this.stopSign = true |
|
||||||
this.sparks = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
this.timer && window.clearInterval(this.timer) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.ctx.fillStyle = 'rgba(0,0,0, 0.1)' |
|
||||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
for (let i = 0; i < this.sparks.length; i++) { |
|
||||||
this.sparks[i].opacity <= 0 ? this.sparks.splice(i, 1) : this.drawSpark(this.sparks[i]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
addSpark() { |
|
||||||
let x = rand(-200, this.canvas.width + 200) |
|
||||||
let y = rand(-200, this.canvas.height + 200) |
|
||||||
this.sparks.push(new Spark(x, y, this.options)) |
|
||||||
} |
|
||||||
|
|
||||||
drawSpark(spark) { |
|
||||||
spark.go(this.options) |
|
||||||
this.ctx.beginPath() |
|
||||||
this.ctx.fillStyle = `rgba(${spark.color},${spark.opacity})` |
|
||||||
this.ctx.rect(spark.x, spark.y, this.options.size[0], this.options.size[1]) |
|
||||||
this.ctx.fill() |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.canvas.width = window.innerWidth |
|
||||||
this.canvas.height = window.innerHeight |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@ -1,3 +0,0 @@ |
|||||||
export function rand(min, max) { |
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min |
|
||||||
} |
|
||||||
@ -1,81 +0,0 @@ |
|||||||
function rand(min, max) { |
|
||||||
return Math.random() * (max - min) + min |
|
||||||
} |
|
||||||
|
|
||||||
export default class Particle { |
|
||||||
constructor() { |
|
||||||
this.reset() |
|
||||||
} |
|
||||||
|
|
||||||
reset() { |
|
||||||
this.x = 0 |
|
||||||
this.y = 0 |
|
||||||
this.z = 0 |
|
||||||
this.vx = rand(-0.5, 0.5) |
|
||||||
this.vy = rand(-0.5, 0.5) |
|
||||||
this.vz = rand(-0.25, 0.5) |
|
||||||
this.s = 0 |
|
||||||
this.sx = 0 |
|
||||||
this.sy = 0 |
|
||||||
this.life = 1 |
|
||||||
this.decay = rand(0.005, 0.02) |
|
||||||
this.radius = rand(5, 15) |
|
||||||
this.sradius = this.radius |
|
||||||
this.rradius = this.radius |
|
||||||
this.hue = 0 |
|
||||||
this.alpha = 1 |
|
||||||
this.angle = 0 |
|
||||||
} |
|
||||||
|
|
||||||
step($) { |
|
||||||
this.vx *= $.mouse.down ? 1.1 : 1.04 |
|
||||||
this.vy *= $.mouse.down ? 1.1 : 1.04 |
|
||||||
this.vz *= $.mouse.down ? 1.1 : 1.04 |
|
||||||
this.x += this.vx |
|
||||||
this.y += this.vy |
|
||||||
this.z += this.vz |
|
||||||
this.s = $.fl / ($.fl + this.z) |
|
||||||
this.sx = this.x * this.s |
|
||||||
this.sy = this.y * this.s |
|
||||||
this.sradius = this.radius * this.s |
|
||||||
this.rradius = Math.max(0.0001, this.sradius * this.life) |
|
||||||
this.angle = Math.atan2(this.sy, this.sx) |
|
||||||
this.hue = (this.angle / (Math.PI * 2)) * 180 + $.tick * 4 |
|
||||||
this.alpha = this.life |
|
||||||
if (this.z < $.bounds.z.min) return this.reset() |
|
||||||
if (this.life > 0) this.life -= this.decay |
|
||||||
else this.reset() |
|
||||||
} |
|
||||||
|
|
||||||
draw($) { |
|
||||||
$.ctx.beginPath() |
|
||||||
$.ctx.arc(this.sx, this.sy, this.rradius * 2, 0, Math.PI * 2) |
|
||||||
$.ctx.fillStyle = 'hsla(' + (this.hue + 60) + ', 60%, 30%, ' + this.alpha / 3 + ')' |
|
||||||
$.ctx.fill() |
|
||||||
$.ctx.strokeStyle = 'hsla(' + (this.hue - 60) + ', 60%, 30%, ' + this.alpha / 2 + ')' |
|
||||||
$.ctx.stroke() |
|
||||||
|
|
||||||
let angle1 = this.angle + Math.PI / 2, |
|
||||||
angle2 = this.angle, |
|
||||||
angle3 = this.angle - Math.PI / 2 |
|
||||||
|
|
||||||
$.ctx.beginPath() |
|
||||||
$.ctx.moveTo(0, 0) |
|
||||||
$.ctx.lineTo(this.sx + Math.cos(angle1) * this.rradius, this.sy + Math.sin(angle1) * this.rradius) |
|
||||||
$.ctx.lineTo(this.sx + Math.cos(angle2) * this.rradius * 6, this.sy + Math.sin(angle2) * this.rradius * 6) |
|
||||||
$.ctx.lineTo(this.sx + Math.cos(angle3) * this.rradius, this.sy + Math.sin(angle3) * this.rradius) |
|
||||||
$.ctx.closePath() |
|
||||||
$.ctx.fillStyle = 'hsla(' + this.hue + ', 50%, 30%, ' + this.alpha / 2 + ')' |
|
||||||
$.ctx.fill() |
|
||||||
|
|
||||||
$.ctx.beginPath() |
|
||||||
$.ctx.moveTo(this.sx + Math.cos(angle2) * this.rradius * 6, this.sy + Math.sin(angle2) * this.rradius * 6) |
|
||||||
$.ctx.lineTo(0, 0) |
|
||||||
$.ctx.strokeStyle = 'hsla(' + this.hue + ', 50%, 30%, ' + this.alpha + ')' |
|
||||||
$.ctx.stroke() |
|
||||||
|
|
||||||
let sparkleRadius = this.rradius * 4 |
|
||||||
$.ctx.fillStyle = 'hsla(' + (this.hue + 180) + ', 100%, 50%, ' + this.alpha * 2 + ')' |
|
||||||
$.ctx.fillRect((this.sx + rand(-sparkleRadius, sparkleRadius)), (this.sy + rand(-sparkleRadius, sparkleRadius)), 1, 1) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,97 +0,0 @@ |
|||||||
import Particle from "./Particle" |
|
||||||
|
|
||||||
export default class Sunlight { |
|
||||||
constructor(canvas) { |
|
||||||
this.canvas = canvas |
|
||||||
this.ctx = canvas.getContext('2d') |
|
||||||
this.parts = [] |
|
||||||
this.mouse = {down: 0} |
|
||||||
this.rAF = null |
|
||||||
this.stopSign = false |
|
||||||
this.loop = this.loop.bind(this) |
|
||||||
this.mousedown = this.mousedown.bind(this) |
|
||||||
this.mouseup = this.mouseup.bind(this) |
|
||||||
this.start() |
|
||||||
} |
|
||||||
|
|
||||||
start() { |
|
||||||
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
||||||
this.resizeObserver.observe(this.canvas) |
|
||||||
window.addEventListener('mouseup', this.mouseup) |
|
||||||
window.addEventListener('mousedown', this.mousedown) |
|
||||||
this.resize() |
|
||||||
this.loop() |
|
||||||
} |
|
||||||
|
|
||||||
stop() { |
|
||||||
if (this.resizeObserver) { |
|
||||||
this.resizeObserver.disconnect() |
|
||||||
this.resizeObserver = null |
|
||||||
} |
|
||||||
window.removeEventListener('mouseup', this.mouseup) |
|
||||||
window.removeEventListener('mousedown', this.mousedown) |
|
||||||
this.stopSign = true |
|
||||||
this.parts = [] |
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) |
|
||||||
} |
|
||||||
|
|
||||||
loop() { |
|
||||||
if (this.stopSign) { |
|
||||||
this.rAF && window.cancelAnimationFrame(this.rAF) |
|
||||||
return |
|
||||||
} |
|
||||||
this.draw() |
|
||||||
this.rAF = window.requestAnimationFrame(this.loop) |
|
||||||
} |
|
||||||
|
|
||||||
draw() { |
|
||||||
this.step() |
|
||||||
this.ctx.globalCompositeOperation = 'destination-out' |
|
||||||
this.ctx.fillStyle = 'hsla(0, 0%, 0%, 0.6)' |
|
||||||
this.ctx.fillRect(0, 0, this.width, this.height) |
|
||||||
this.ctx.globalCompositeOperation = 'lighter' |
|
||||||
this.ctx.save() |
|
||||||
this.ctx.translate(this.width / 2, this.height / 2) |
|
||||||
this.ctx.rotate(this.tick / 300) |
|
||||||
let i = this.parts.length |
|
||||||
while (i--) { |
|
||||||
this.parts[i].draw(this) |
|
||||||
} |
|
||||||
this.ctx.restore() |
|
||||||
} |
|
||||||
|
|
||||||
step() { |
|
||||||
if (this.tick % 2 === 0 && this.parts.length < 200) { |
|
||||||
this.parts.push(new Particle()) |
|
||||||
} |
|
||||||
let i = this.parts.length |
|
||||||
while (i--) { |
|
||||||
this.parts[i].step(this) |
|
||||||
} |
|
||||||
this.tick += this.mouse.down ? 3 : 1 |
|
||||||
} |
|
||||||
|
|
||||||
mousedown() { |
|
||||||
this.mouse.down = 1 |
|
||||||
} |
|
||||||
|
|
||||||
mouseup() { |
|
||||||
this.mouse.down = 0 |
|
||||||
} |
|
||||||
|
|
||||||
resize() { |
|
||||||
this.tick = 0 |
|
||||||
this.width = window.innerWidth |
|
||||||
this.height = window.innerHeight |
|
||||||
this.canvas.width = this.width |
|
||||||
this.canvas.height = this.height |
|
||||||
this.mouse.down = 0 |
|
||||||
this.fl = 300 |
|
||||||
this.bounds = { |
|
||||||
x: {min: -this.width / 2, max: this.width / 2}, |
|
||||||
y: {min: -this.height / 2, max: this.height / 2}, |
|
||||||
z: {min: -this.fl, max: 1000} |
|
||||||
} |
|
||||||
this.parts.length = 0 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,16 +1,11 @@ |
|||||||
export default { |
export default { |
||||||
path: 'directive', |
path: 'directive', |
||||||
meta: {title: '全局指令', icon: 'el-icon-s-grid'}, |
meta: {title: '全局指令', icon: 'el-icon-s-grid', alwaysShow: true}, |
||||||
children: [ |
children: [ |
||||||
{ |
{ |
||||||
path: 'dragDialog', |
path: 'dragDialog', |
||||||
component: 'example/directive/dragDialog', |
component: 'example/directive/dragDialog', |
||||||
meta: {title: '可拖拽dialog'} |
meta: {title: '可拖拽dialog'} |
||||||
}, |
|
||||||
{ |
|
||||||
path: 'ripple', |
|
||||||
component: 'example/directive/ripple', |
|
||||||
meta: {title: '波纹'} |
|
||||||
} |
} |
||||||
] |
] |
||||||
} |
} |
||||||
|
|||||||
@ -1,21 +1,11 @@ |
|||||||
export default { |
export default { |
||||||
path: 'global', |
path: 'global', |
||||||
meta: {title: '全局方法', icon: 'el-icon-s-opportunity'}, |
meta: {title: '全局方法', icon: 'el-icon-s-opportunity', alwaysShow: true}, |
||||||
children: [ |
children: [ |
||||||
{ |
{ |
||||||
path: 'message', |
path: 'message', |
||||||
component: 'example/globalMethod/message', |
component: 'example/globalMethod/message', |
||||||
meta: {title: '消息提示'} |
meta: {title: '消息提示'} |
||||||
}, |
|
||||||
{ |
|
||||||
path: 'imageViewer', |
|
||||||
component: 'example/globalMethod/imageViewer', |
|
||||||
meta: {title: '图片预览'} |
|
||||||
}, |
|
||||||
{ |
|
||||||
path: 'verify', |
|
||||||
component: 'example/globalMethod/verify', |
|
||||||
meta: {title: '拼图验证'} |
|
||||||
} |
} |
||||||
] |
] |
||||||
} |
} |
||||||
|
|||||||
@ -1,14 +0,0 @@ |
|||||||
import {createMutations} from "@/store/util" |
|
||||||
|
|
||||||
const state = { |
|
||||||
//登录页背景动画
|
|
||||||
loginBackgroundAnimation: 'sparkRain', |
|
||||||
} |
|
||||||
|
|
||||||
const mutations = createMutations(state) |
|
||||||
|
|
||||||
export default { |
|
||||||
namespaced: true, |
|
||||||
state, |
|
||||||
mutations |
|
||||||
} |
|
||||||
@ -1,49 +0,0 @@ |
|||||||
<script type="text/jsx"> |
|
||||||
const animations = [ |
|
||||||
{name: '无', value: ''}, |
|
||||||
{name: '烟花', value: 'firework'}, |
|
||||||
{name: '上帝之光', value: 'godrays'}, |
|
||||||
{name: '简单粒子', value: 'moveFollowMouse'}, |
|
||||||
{name: '球', value: 'particleBall'}, |
|
||||||
{name: '网络', value: 'particleNetwork'}, |
|
||||||
{name: '波浪', value: 'particleWave'}, |
|
||||||
{name: '雨', value: 'reflectRain'}, |
|
||||||
{name: '流星雨', value: 'sparkRain'}, |
|
||||||
{name: '阳光', value: 'sunlight'}, |
|
||||||
] |
|
||||||
|
|
||||||
export default { |
|
||||||
name: "SetAnimation", |
|
||||||
|
|
||||||
functional: true, |
|
||||||
|
|
||||||
props: { |
|
||||||
value: String, |
|
||||||
customClass: String |
|
||||||
}, |
|
||||||
|
|
||||||
render(h, context) { |
|
||||||
const {value, customClass} = context.props |
|
||||||
return ( |
|
||||||
<el-dropdown class={customClass} size="medium" trigger="click" onCommand={context.listeners.select}> |
|
||||||
<el-button type="text" title="选择背景动画"> |
|
||||||
<v-icon icon="svg-show"/> |
|
||||||
</el-button> |
|
||||||
<el-dropdown-menu slot="dropdown"> |
|
||||||
{ |
|
||||||
animations.map(animation => ( |
|
||||||
<el-dropdown-item |
|
||||||
key={animation.value} |
|
||||||
disabled={animation.value === value} |
|
||||||
command={animation.value} |
|
||||||
> |
|
||||||
{animation.name} |
|
||||||
</el-dropdown-item> |
|
||||||
)) |
|
||||||
} |
|
||||||
</el-dropdown-menu> |
|
||||||
</el-dropdown> |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
@ -1,57 +0,0 @@ |
|||||||
<template> |
|
||||||
<div> |
|
||||||
<div class="tip-row"> |
|
||||||
波纹,详情请见<a href="https://quasar.dev/vue-directives/material-ripple" target="_blank">quasar</a> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div v-ripple.early class="ripple-example" :style="style">点我触发</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
export default { |
|
||||||
name: "ripplePage", |
|
||||||
|
|
||||||
data() { |
|
||||||
return { |
|
||||||
index: 0, |
|
||||||
colors: ['amber', 'orange', 'accent', 'lime', 'cyan', 'purple', 'brown', 'blue'] |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
computed: { |
|
||||||
style() { |
|
||||||
return {'background-color': this.colors[this.index]} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
mounted() { |
|
||||||
this.timer = window.setInterval(() => { |
|
||||||
this.index = (this.index + 1) % this.colors.length |
|
||||||
this.color = this.colors[this.index] |
|
||||||
}, 3000) |
|
||||||
|
|
||||||
this.$once('hook:beforeDestroy', () => { |
|
||||||
window.clearInterval(this.timer) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.ripple-example { |
|
||||||
display: flex; |
|
||||||
flex-wrap: wrap; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
margin-top: 50px; |
|
||||||
height: 150px; |
|
||||||
width: 80%; |
|
||||||
max-width: 500px; |
|
||||||
border-radius: 3px; |
|
||||||
cursor: pointer; |
|
||||||
transition: background 1.5s; |
|
||||||
color: #ffffff; |
|
||||||
user-select: none; |
|
||||||
} |
|
||||||
</style> |
|
||||||
@ -1,33 +0,0 @@ |
|||||||
<template> |
|
||||||
<div> |
|
||||||
<div class="tip-row"> |
|
||||||
这是对<a href="https://github.com/sachinchoolur/lightgallery.js" target="_blank">lightgallary</a>的简单封装 |
|
||||||
</div> |
|
||||||
|
|
||||||
<div> |
|
||||||
<img v-for="(i,index) in images" :key="i" :src="i" @click="preview(index)"> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
export default { |
|
||||||
name: "imageViewerPage", |
|
||||||
|
|
||||||
data() { |
|
||||||
return { |
|
||||||
images: [ |
|
||||||
'https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4018557288,1217151095&fm=26&gp=0.jpg', |
|
||||||
'https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3252521864,872614242&fm=26&gp=0.jpg', |
|
||||||
'https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg' |
|
||||||
] |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
methods: { |
|
||||||
preview(index) { |
|
||||||
this.$image({index, urlList: this.images}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
@ -1,25 +0,0 @@ |
|||||||
<template> |
|
||||||
<div> |
|
||||||
<div class="tip-row"> |
|
||||||
拼图验证, |
|
||||||
在<a href="https://github.com/Kevin-269581661/vue-puzzle-verification" target="_blank"></a>的基础上做了样式修改 |
|
||||||
</div> |
|
||||||
<el-button type="primary" @click="onClick">触发验证</el-button> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
import {elSuccess, elError} from "@/util/message" |
|
||||||
|
|
||||||
export default { |
|
||||||
name: "verifyPage", |
|
||||||
|
|
||||||
methods: { |
|
||||||
onClick() { |
|
||||||
this.$verify() |
|
||||||
.then(() => elSuccess('验证通过!')) |
|
||||||
.catch(() => elError('验证失败!')) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||