HTML5canvas万花筒的绘制,HTML5 Canvas Rx.js实现的万花筒画笔

JavaScript

语言:

JaveScriptBabelCoffeeScript

确定

// CONSTANTS: CHANGE THESE

// Number of reflected sides

const sides = 7;

// Radius of the dot we're drawing.

const radius = 5;

// (100 - ?) The timing, in ms, of the feedback delay.

// If you bring this number way down you might

// want to turn `fadeout` up to 0.4 or 0.5.

const delay = 1500;

// (0 - 1) how much opacity to apply on each

// frame to clear the screen. Lower = slower,

// higher = faster.

const fadeout = 0.1;

// (0 - 360) Hue, in degrees, of the dot you're

// currently drawing.

const colorStart = 0;

// (0 - ?) How far should the color rotate across

// the reflected sides? 360 = one full rainbow.

const colorSpread = 360;

// SETUP

// We'll use this ID to make the feedback loop erasable.

let lastId = 0;

// Maybe scale the canvas for a retina display.

const scale = window.devicePixelRatio;

// Grab the canvas and set the width and height.

const canvasElement = document.getElementById('canvas');

canvas.width = window.innerWidth * scale;

canvas.height = window.innerHeight * scale;

const context = canvas.getContext('2d');

// An erase function we'll use when the window gets resized.

const erase = () => context.clearRect(0, 0, window.innerWidth * scale, window.innerHeight * scale);

// On resize set the canvas width & height so there's no stretching.

window.onresize = () => {

canvasElement.width = window.innerWidth * scale;

canvasElement.height = window.innerHeight * scale;

};

// STREAMS

// A stream of mouse movement.

const mouseMovement = Rx.Observable.fromEvent(document.querySelector('body'), 'mousemove');

// For some reason `fromEvent` isn't working for touchmove so we'll make our own stream.

const touchMovement = new Rx.Subject();

document.addEventListener('touchmove', e => {

e.stopPropagation();

touchMovement.next(e);

});

// A special stream that will create a feedback loop,

// continuously processing the same information on

// a delay.

const feedback = new Rx.Subject();

// A stream of clear button clicks.

const clearDrawing = Rx.Observable.fromEvent(document.querySelector('#clear'), 'click');

// A stream of requestAnimationFrame callbacks for doing work at 60fps.

const frames = Rx.Observable.create(observable => {

const frame = () => {

window.requestAnimationFrame(frame);

observable.next();

}

frame();

});

// LOGIC

// Using the mouse movement as input we're going to set up

// a feedback loop that endlessly replays our event data.

mouseMovement

.merge(touchMovement)

// Only send movement events if the mouse button is pressed or if it's a touch event.

.filter(e => {

if (e.targetTouches) return true;

return e.button === 1 || e.which === 1;

})

// We don't want or need to pass the full event object around

// so we map the stream to only the values we need. We also

// assign an ID before we enter the feedback loop, which allows

// us to clear the current drawings by filtering out old IDs.

.map(e => {

if (e.targetTouches) return {

x: e.targetTouches[0].clientX,

y: e.targetTouches[0].clientY,

id: lastId

};

return {

x: e.clientX,

y: e.clientY,

id: lastId

};

})

// Merge the feedback stream into this one, allowing us to

// replay previously observed events as if they were new.

.merge(feedback)

// Now we're in feedback territory! Filter out any events with

// an old ID. The ID will be incremented using the clear button.

.filter(e => e.id === lastId)

// On each event draw the coordinates to the canvas. We're mapping

// here rather than subscribing because we still have to pipe

// the data into the feedback loop. If we subscribed we wouldn't

// be able to further modify the stream.

.map(e => {

// We're going to draw the data once for every symmetrical side.

for (let i = 0; i < sides; i++) {

// rotate the original coordinates around the center of the canvas.

const {

rx, ry

} = rotateAroundCenter(e.x, e.y, (Math.PI * 2) / sides * i);

// Set the hue based on the current rotation.

context.fillStyle = `hsl(${colorStart + (colorSpread / sides * i)}, 100%, 50%)`;

// Draw a circle •

context.beginPath();

context.arc(rx * scale, ry * scale, radius, 0, Math.PI * 2);

context.fill();

}

// We're using map so we must return the event.

return e;

})

// Delay the event for the specified number of ms.

.delay(Math.max(100, delay))

// Take the delayed event and push it into the feedback stream.

.subscribe(e => feedback.next(e));

// when the clear button is pressed we increment

// the lastId, filtering out any old events.

clearDrawing.subscribe(e => {

lastId = lastId + 1;

erase();

});

// Each frame, fade the canvas out a bit by putting a

// semi-transparent white box over everything. The

// `fadeout` const determins how much opacity to apply.

frames.subscribe(() => {

context.fillStyle = `rgba(255, 255, 255, ${fadeout})`;

context.fillRect(0, 0, window.innerWidth * scale, window.innerHeight * scale);

})

// HELPERS

// A function for rotating a point around the center

// of the canvas by `angle` radians. We're working with

// screen coordinates here — not canvas coordinates —

// so we don't apply the `scale`.

function rotateAroundCenter(xpos, ypos, angle) {

const cx = window.innerWidth / 2;

const cy = window.innerHeight / 2;

const rx = Math.cos(angle) * (xpos - cx) - Math.sin(angle) * (ypos - cy) + cx;

const ry = Math.sin(angle) * (xpos - cx) + Math.cos(angle) * (ypos - cy) + cy;

return {

rx, ry

};

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值