केस स्टडी - Technitone.com बनाना

Sean Middleditch
Sean Middleditch
Technitone — वेब ऑडियो का अनुभव.

Technitone.com, Chrome में WebGL, कैनवस, वेब सॉकेट, सीएसएस3, JavaScript, Flash, और नए Web Audio API का फ़्यूज़न है.

इस लेख में, प्रोडक्शन के हर पहलू के बारे में बताया गया है: प्लान, सर्वर, साउंड, विज़ुअल, और कुछ वर्कफ़्लो. इनका इस्तेमाल, इंटरैक्टिव डिज़ाइन के लिए किया जाता है. ज़्यादातर सेक्शन में कोड स्निपेट, डेमो, और डाउनलोड शामिल होते हैं. लेख के आखिर में, डाउनलोड करने का लिंक दिया गया है. यहां से सभी फ़ाइलों को एक ज़िप फ़ाइल के तौर पर डाउनलोड किया जा सकता है.

gskinner.com की प्रोडक्शन टीम.

कॉन्टेंट

हम gskinner.com पर ऑडियो इंजीनियर नहीं हैं — लेकिन हमें किसी चैलेंज के बारे में बताएं, हम कोई योजना बना देंगे:

  • उपयोगकर्ता, ऐंड्री के ToneMatrix से "प्रेरित" होकर, ग्रिड पर टोन प्लॉट करते हैं
  • टोन, सैंपल किए गए इंस्ट्रूमेंट, ड्रम किट या उपयोगकर्ताओं की रिकॉर्डिंग से कनेक्ट होते हैं
  • कनेक्ट किए गए कई उपयोगकर्ता एक ही ग्रिड पर एक साथ खेलते हैं
  • …या अपने हिसाब से एक्सप्लोर करने के लिए, सोलो मोड में जाएं
  • न्योते वाले सेशन की मदद से, उपयोगकर्ता एक बैंड बना सकते हैं और अचानक से संगीत बना सकते हैं

हम उपयोगकर्ताओं को एक टूल पैनल की मदद से, वेब ऑडियो एपीआई को एक्सप्लोर करने का मौका देते हैं. इस टूल पैनल की मदद से, उपयोगकर्ता अपनी आवाज़ पर ऑडियो फ़िल्टर और इफ़ेक्ट लागू कर सकते हैं.

gskinner.com का Technitone

हम ये काम भी करते हैं:

  • उपयोगकर्ताओं की कंपोजिशन और इफ़ेक्ट को डेटा के तौर पर सेव करना और उन्हें सभी क्लाइंट पर सिंक करना
  • रंग के कुछ विकल्प दें, ताकि वे शानदार गाने बना सकें
  • गैलरी की सुविधा उपलब्ध कराएं, ताकि लोग दूसरे लोगों के काम को सुन सकें, पसंद कर सकें या उसमें बदलाव कर सकें

हमने ग्रिड के सामान्य मेटाफ़ोर का इस्तेमाल किया है. साथ ही, इसे 3D स्पेस में फ़्लोट किया है. इसमें कुछ लाइटिंग, टेक्स्चर, और पार्टिकल इफ़ेक्ट जोड़े हैं. साथ ही, इसे फ़्लेक्सिबल (या फ़ुलस्क्रीन) सीएसएस और JS-ड्रिवन इंटरफ़ेस में रखा है.

रोड ट्रिप

इंस्ट्रूमेंट, इफ़ेक्ट, और ग्रिड डेटा को क्लाइंट पर इकट्ठा और क्रम से लगाया जाता है. इसके बाद, इसे Socket.io की तरह कई उपयोगकर्ताओं के लिए हल करने के लिए, हमारे कस्टम Node.js बैकएंड पर भेजा जाता है. यह डेटा, क्लाइंट को वापस भेजा जाता है. इसमें हर खिलाड़ी के योगदान को शामिल किया जाता है. इसके बाद, इसे कई उपयोगकर्ताओं के साथ वीडियो चलाने के दौरान यूज़र इंटरफ़ेस (यूआई), सैंपल, और इफ़ेक्ट को रेंडर करने वाली संबंधित सीएसएस, WebGL, और WebAudio लेयर में भेजा जाता है.

सॉकेट की मदद से रीयल-टाइम कम्यूनिकेशन, क्लाइंट पर JavaScript और सर्वर पर JavaScript फ़ीड करता है.

Technitone सर्वर डायग्राम

हम सर्वर के हर हिस्से के लिए Node का इस्तेमाल करते हैं. यह एक स्टैटिक वेब सर्वर और हमारा सॉकेट सर्वर, दोनों एक ही है. हमने Express का इस्तेमाल किया. यह एक ऐसा वेब सर्वर है जिसे पूरी तरह से Node पर बनाया गया है. इसे आसानी से स्केल किया जा सकता है और इसमें अपने हिसाब से बदलाव किए जा सकते हैं. साथ ही, यह आपके लिए लो-लेवल सर्वर के कामों को मैनेज करता है, ठीक उसी तरह जैसे Apache या Windows Server करता है. इसके बाद, आपको डेवलपर के तौर पर सिर्फ़ अपने ऐप्लिकेशन को बनाने पर ध्यान देना होगा.

मल्टी-यूज़र डेमो (ठीक है, यह सिर्फ़ एक स्क्रीनशॉट है)

इस डेमो को किसी नोड सर्वर से चलाना ज़रूरी है. इस लेख में ऐसा नहीं किया गया है. इसलिए, हमने एक स्क्रीनशॉट शामिल किया है. इसमें बताया गया है कि Node.js इंस्टॉल करने, वेब सर्वर को कॉन्फ़िगर करने, और उसे लोकल तौर पर चलाने के बाद, डेमो कैसा दिखता है. जब भी कोई नया उपयोगकर्ता आपके डेमो इंस्टॉलेशन पर जाएगा, तब एक नया ग्रिड जुड़ जाएगा. साथ ही, सभी का काम एक-दूसरे को दिखेगा.

Node.js डेमो का स्क्रीनशॉट

नोड आसान है. Socket.io और कस्टम POST अनुरोधों के कॉम्बिनेशन का इस्तेमाल करके, हमें सिंक करने के लिए जटिल रूटीन बनाने की ज़रूरत नहीं पड़ी. Socket.io, इस प्रोसेस को पारदर्शी तरीके से मैनेज करता है. इसमें JSON का इस्तेमाल किया जाता है.

कितना आसान है? यह देखें.

JavaScript की तीन लाइनों की मदद से, हमारे पास Express के साथ काम करने वाला वेब सर्वर है.

//Tell  our Javascript file we want to use express.
var express = require('express');

//Create our web-server
var server = express.createServer();

//Tell express where to look for our static files.
server.use(express.static(__dirname + '/static/'));

रीयल-टाइम कम्यूनिकेशन के लिए, socket.io को जोड़ने के लिए कुछ और.

var io = require('socket.io').listen(server);
//Start listening for socket commands
io.sockets.on('connection', function (socket) {
    //User is connected, start listening for commands.
    socket.on('someEventFromClient', handleEvent);

});

अब हम HTML पेज से आने वाले कनेक्शन सुनना शुरू कर देते हैं.

<!-- Socket-io will serve it-self when requested from this url. -->
<script type="text/javascript" src="/socket.io/socket.io.js"></script>

 <!-- Create our socket and connect to the server -->
 var sock = io.connect('http://localhost:8888');
 sock.on("connect", handleConnect);

 function handleConnect() {
    //Send a event to the server.
    sock.emit('someEventFromClient', 'someData');
 }
 ```

## Sound check

A big unknown was the effort entailed with using the Web Audio API. Our initial findings confirmed that [Digital Signal Processing](http://en.wikipedia.org/wiki/Digital_Signal_Processing) (DSP) is very complex, and we were likely in way over our heads. Second realization: [Chris Rogers](http://chromium.googlecode.com/svn/trunk/samples/audio/index.html) has already done the heavy lifting in the API.
Technitone isn't using any really complex math or audioholicism; this functionality is easily accessible to interested developers. We really just needed to brush up on some terminology and [read the docs](https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html). Our advice? Don't skim them. Read them. Start at the top and end at the bottom. They are peppered with diagrams and photos, and it's really cool stuff.

If this is the first you've heard of the Web Audio API, or don't know what it can do, hit up Chris Rogers' [demos](http://chromium.googlecode.com/svn/trunk/samples/audio/index.html). Looking for inspiration? You'll definitely find it there.

### Web Audio API Demo

Load in a sample (sound file)…

```js
/**
 * The XMLHttpRequest allows you to get the load
 * progress of your file download and has a responseType
 * of "arraybuffer" that the Web Audio API uses to
 * create its own AudioBufferNode.
 * Note: the 'true' parameter of request.open makes the
 * request asynchronous - this is required!
 */
var request = new XMLHttpRequest();
request.open("GET", "mySample.mp3", true);
request.responseType = "arraybuffer";
request.onprogress = onRequestProgress; // Progress callback.
request.onload = onRequestLoad; // Complete callback.
request.onerror = onRequestError; // Error callback.
request.onabort = onRequestError; // Abort callback.
request.send();

// Use this context to create nodes, route everything together, etc.
var context = new webkitAudioContext();

// Feed this AudioBuffer into your AudioBufferSourceNode:
var audioBuffer = null;

function onRequestProgress (event) {
    var progress = event.loaded / event.total;
}

function onRequestLoad (event) {
    // The 'true' parameter specifies if you want to mix the sample to mono.
    audioBuffer = context.createBuffer(request.response, true);
}

function onRequestError (event) {
    // An error occurred when trying to load the sound file.
}

…मॉड्यूलर रूटिंग सेट अप करना…

/**
 * Generally you'll want to set up your routing like this:
 * AudioBufferSourceNode > [effect nodes] > CompressorNode > AudioContext.destination
 * Note: nodes are designed to be able to connect to multiple nodes.
 */

// The DynamicsCompressorNode makes the loud parts
// of the sound quieter and quiet parts louder.
var compressorNode = context.createDynamicsCompressor();
compressorNode.connect(context.destination);

// [other effect nodes]

// Create and route the AudioBufferSourceNode when you want to play the sample.

…रनटाइम इफ़ेक्ट लागू करें (इंपल्स रिस्पॉन्स का इस्तेमाल करके कन्वोल्यूशन)…

/**
 * Your routing now looks like this:
 * AudioBufferSourceNode > ConvolverNode > CompressorNode > AudioContext.destination
 */

var convolverNode = context.createConvolver();
convolverNode.connect(compressorNode);
convolverNode.buffer = impulseResponseAudioBuffer;

…रनटाइम इफ़ेक्ट (देरी) लागू करें…

/**
 * The delay effect needs some special routing.
 * Unlike most effects, this one takes the sound data out
 * of the flow, reinserts it after a specified time (while
 * looping it back into itself for another iteration).
 * You should add an AudioGainNode to quieten the
 * delayed sound...just so things don't get crazy :)
 *
 * Your routing now looks like this:
 * AudioBufferSourceNode -> ConvolverNode > CompressorNode > AudioContext.destination
 *                       |  ^
 *                       |  |___________________________
 *                       |  v                          |
 *                       -> DelayNode > AudioGainNode _|
 */

var delayGainNode = context.createGainNode();
delayGainNode.gain.value = 0.7; // Quieten the feedback a bit.
delayGainNode.connect(convolverNode);

var delayNode = context.createDelayNode();
delayNode.delayTime = 0.5; // Re-sound every 0.5 seconds.
delayNode.connect(delayGainNode);

delayGainNode.connect(delayNode); // make the loop

…और फिर उसे सुनने लायक बनाएं.

/**
 * Once your routing is set up properly, playing a sound
 * is easy-shmeezy. All you need to do is create an
 * AudioSourceBufferNode, route it, and tell it what time
 * (in seconds relative to the currentTime attribute of
 * the AudioContext) it needs to play the sound.
 *
 * 0 == now!
 * 1 == one second from now.
 * etc...
 */

var sourceNode = context.createBufferSource();
sourceNode.connect(convolverNode);
sourceNode.connect(delayNode);
sourceNode.buffer = audioBuffer;
sourceNode.noteOn(0); // play now!

Technitone में वीडियो चलाने का हमारा तरीका, शेड्यूल करने के बारे में है. हर बीट में साउंड प्रोसेस करने के लिए, हम अपने टेंपो के बराबर का टाइमर इंटरवल सेट करने के बजाय, एक छोटा इंटरवल सेट अप करते हैं. यह इंटरवल, साउंड को एक लाइन में मैनेज और शेड्यूल करता है. इससे एपीआई, ऑडियो डेटा को हल करने और फ़िल्टर और इफ़ेक्ट को प्रोसेस करने का काम पहले से कर लेता है. इसके बाद, सीपीयू को ऑडियो को सुनने लायक बनाने का काम सौंपा जाता है. जब वह बीट आती है, तब उसके पास स्पीकर को नतीजा दिखाने के लिए ज़रूरी जानकारी पहले से मौजूद होती है.

कुल मिलाकर, सब कुछ ऑप्टिमाइज़ करना ज़रूरी था. जब हमने अपने सीपीयू पर बहुत ज़्यादा दबाव डाला, तो शेड्यूल के मुताबिक काम करने के लिए, प्रोसेस (पॉप, क्लिक, स्क्रैच) को छोड़ दिया गया. अगर आपने Chrome में किसी दूसरे टैब पर स्विच किया, तो हमने इस समस्या को ठीक करने की पूरी कोशिश की.

लाइट शो

सबसे आगे और बीच में, हमारा ग्रिड और पार्टिकल टनल है. यह Technitone की WebGL लेयर है.

वेब पर विज़ुअल रेंडर करने के लिए, WebGL अन्य तरीकों के मुकाबले काफ़ी बेहतर परफ़ॉर्म करता है. ऐसा इसलिए होता है, क्योंकि यह जीपीयू को प्रोसेसर के साथ मिलकर काम करने का टास्क देता है. हालांकि, इस परफ़ॉर्मेंस में बढ़ोतरी के लिए, ज़्यादा डेवलपमेंट की ज़रूरत होती है. साथ ही, इसमें सीखने की प्रक्रिया भी ज़्यादा मुश्किल होती है. हालांकि, अगर आपको वेब पर इंटरैक्टिव कॉन्टेंट बनाने में दिलचस्पी है और आपको परफ़ॉर्मेंस से जुड़ी कम से कम पाबंदियां चाहिए, तो WebGL एक ऐसा समाधान है जो Flash के बराबर है.

WebGL डेमो

WebGL कॉन्टेंट को कैनवस (असल में, एचटीएमएल5 कैनवस) पर रेंडर किया जाता है. इसमें ये मुख्य बिल्डिंग ब्लॉक शामिल होते हैं:

  • ऑब्जेक्ट के वर्टिकल (ज्यामिति)
  • पोज़िशन मैट्रिक्स (3D निर्देशांक)
    • शेडर (ज्यामिति के दिखने का ब्यौरा, सीधे तौर पर GPU से जुड़ा)
    • कॉन्टेक्स्ट ("शॉर्टकट" उन एलिमेंट के लिए जो जीपीयू रेफ़र करता है)
    • बफ़र (जीपीयू को कॉन्टेक्स्ट डेटा भेजने के लिए पाइपलाइन)
    • मुख्य कोड (पसंदीदा इंटरैक्टिव के हिसाब से कारोबारी लॉजिक)
    • "draw" तरीका (शेडर चालू करता है और कैनवस पर पिक्सल बनाता है)

स्क्रीन पर WebGL कॉन्टेंट को रेंडर करने की बुनियादी प्रोसेस इस तरह की होती है:

  1. पर्सपेक्टिव मैट्रिक सेट करें. यह 3D स्पेस में दिखने वाले कैमरे की सेटिंग अडजस्ट करता है. साथ ही, पिक्चर प्लेन तय करता है.
  2. पोज़िशन मैट्रिक सेट करें (3D निर्देशांक में ऑरिजिन की जानकारी दें, जिससे पोज़िशन को मेज़र किया जाता है).
  3. शेडर के ज़रिए कॉन्टेक्स्ट में भेजने के लिए, बफ़र को डेटा (वेक्टर पोज़िशन, रंग, टेक्सचर वगैरह) से भरें.
  4. शेडर की मदद से बफ़र से डेटा निकालें और उसे व्यवस्थित करें. इसके बाद, उसे GPU में भेजें.
  5. ड्रॉ करने के तरीके को कॉल करके, संदर्भ को शेडर चालू करने, डेटा के साथ चलाने, और कैनवस को अपडेट करने के लिए कहें.

यह इस तरह दिखता है:

पर्सपेक्टिव मैट्रिक सेट करें…

// Aspect ratio (usually based off the viewport,
// as it can differ from the canvas dimensions).
var aspectRatio = canvas.width / canvas.height;

// Set up the camera view with this matrix.
mat4.perspective(45, aspectRatio, 0.1, 1000.0, pMatrix);

// Adds the camera to the shader. [context = canvas.context]
// This will give it a point to start rendering from.
context.uniformMatrix4fv(shader.pMatrixUniform, 0, pMatrix);

…पोज़िशन मैट्रिक सेट करें…

// This resets the mvMatrix. This will create the origin in world space.
mat4.identity(mvMatrix);

// The mvMatrix will be moved 20 units away from the camera (z-axis).
mat4.translate(mvMatrix, [0,0,-20]);

// Sets the mvMatrix in the shader like we did with the camera matrix.
context.uniformMatrix4fv(shader.mvMatrixUniform, 0, mvMatrix);

…ज्यामिति और दिखावट तय करें…

// Creates a square with a gradient going from top to bottom.
// The first 3 values are the XYZ position; the last 4 are RGBA.
this.vertices = new Float32Array(28);
this.vertices.set([-2,-2, 0,    0.0, 0.0, 0.7, 1.0,
                   -2, 2, 0,    0.0, 0.4, 0.9, 1.0,
                    2, 2, 0,    0.0, 0.4, 0.9, 1.0,
                    2,-2, 0,    0.0, 0.0, 0.7, 1.0
                  ]);

// Set the order of which the vertices are drawn. Repeating values allows you
// to draw to the same vertex again, saving buffer space and connecting shapes.
this.indices = new Uint16Array(6);
this.indices.set([0,1,2, 0,2,3]);

…बफ़र को डेटा से भरें और उसे कॉन्टेक्स्ट में पास करें…

// Create a new storage space for the buffer and assign the data in.
context.bindBuffer(context.ARRAY_BUFFER, context.createBuffer());
context.bufferData(context.ARRAY_BUFFER, this.vertices, context.STATIC_DRAW);

// Separate the buffer data into its respective attributes per vertex.
context.vertexAttribPointer(shader.vertexPositionAttribute,3,context.FLOAT,0,28,0);
context.vertexAttribPointer(shader.vertexColorAttribute,4,context.FLOAT,0,28,12);

// Create element array buffer for the index order.
context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, context.createBuffer());
context.bufferData(context.ELEMENT_ARRAY_BUFFER, this.indices, context.STATIC_DRAW);

…और draw फ़ंक्शन को कॉल करें

// Draw the triangles based off the order: [0,1,2, 0,2,3].
// Draws two triangles with two shared points (a square).
context.drawElements(context.TRIANGLES, 6, context.UNSIGNED_SHORT, 0);

अगर आपको अल्फा-आधारित विज़ुअल एक-दूसरे के ऊपर स्टैक नहीं करने हैं, तो हर फ़्रेम में कैनवस को साफ़ करना न भूलें.

द वेन्यू

ग्रिड और पार्टिकल टनल के अलावा, हर दूसरे यूज़र इंटरफ़ेस (यूआई) एलिमेंट को एचटीएमएल / सीएसएस और JavaScript में इंटरैक्टिव लॉजिक के साथ बनाया गया था.

शुरुआत से ही, हमने यह तय किया था कि उपयोगकर्ताओं को ग्रिड के साथ जल्द से जल्द इंटरैक्ट करना चाहिए. कोई स्प्लैश स्क्रीन, कोई निर्देश, कोई ट्यूटोरियल नहीं, सिर्फ़ 'शुरू करें'. अगर इंटरफ़ेस लोड हो गया है, तो उसे कोई भी चीज़ धीमा नहीं करनी चाहिए.

इसलिए, हमें इस बात पर ध्यान देना पड़ा कि पहली बार इस्तेमाल करने वाले उपयोगकर्ता को इंटरैक्शन के दौरान कैसे गाइड किया जाए. हमने कुछ छोटे-मोटे संकेत शामिल किए हैं. जैसे, WebGL स्पेस में उपयोगकर्ता के माउस की पोज़िशन के आधार पर, सीएसएस कर्सर प्रॉपर्टी में बदलाव होना. अगर कर्सर ग्रिड पर है, तो हम उसे हाथ के आकार वाले कर्सर में बदल देते हैं. ऐसा इसलिए किया जाता है, क्योंकि टोन प्लॉट करके इंटरैक्ट किया जा सकता है. अगर कर्सर को ग्रिड के आस-पास के खाली हिस्से पर घुमाया जाता है, तो हम उसे डायरेक्शनल क्रॉस कर्सर से बदल देते हैं. इससे यह पता चलता है कि ग्रिड को घुमाया जा सकता है या लेयर में बांटा जा सकता है.

शो के लिए तैयारी करना

LESS (सीएसएस प्री-प्रोसेसर) और CodeKit (वेब डेवलपमेंट पर स्टेरॉइड) की मदद से, डिज़ाइन फ़ाइलों को स्टब किए गए एचटीएमएल/सीएसएस में बदलने में लगने वाला समय काफ़ी कम हो जाता है. इनकी मदद से, वैरिएबल, मिक्स-इन (फ़ंक्शन), और गणित का इस्तेमाल करके, सीएसएस को ज़्यादा बेहतर तरीके से व्यवस्थित, लिखा, और ऑप्टिमाइज़ किया जा सकता है!

स्टेज इफ़ेक्ट

CSS3 ट्रांज़िशन और backbone.js का इस्तेमाल करके, हमने कुछ आसान इफ़ेक्ट बनाए हैं. इनसे ऐप्लिकेशन को ज़्यादा बेहतर बनाने में मदद मिलती है. साथ ही, उपयोगकर्ताओं को विज़ुअल क्यू मिलते हैं, ताकि वे यह जान सकें कि वे किस इंस्ट्रूमेंट का इस्तेमाल कर रहे हैं.

Technitone के रंग.

Backbone.js की मदद से, कलर में हुए बदलावों के इवेंट को कैप्चर किया जा सकता है. साथ ही, सही डीओएम एलिमेंट पर नया रंग लागू किया जा सकता है. जीपीयू की मदद से तेज़ी से होने वाले सीएसएस3 ट्रांज़िशन की मदद से, कलर स्टाइल में बदलाव किए गए. इससे परफ़ॉर्मेंस पर बहुत कम या कोई असर नहीं पड़ा.

इंटरफ़ेस एलिमेंट पर, ज़्यादातर कलर ट्रांज़िशन, बैकग्राउंड के रंगों को ट्रांज़िशन करके बनाए गए थे. इस बैकग्राउंड के रंग के ऊपर, हम बैकग्राउंड इमेज को रणनीतिक तौर पर पारदर्शी जगहों पर रखते हैं, ताकि बैकग्राउंड का रंग चमक सके.

एचटीएमएल: बुनियादी बातें

डेमो के लिए, हमें तीन कलर रीजन की ज़रूरत थी: उपयोगकर्ता के चुने गए दो कलर रीजन और तीसरा मिक्स कलर रीजन. हमने अपने इलस्ट्रेशन के लिए, सीएसएस3 ट्रांज़िशन और सबसे कम एचटीटीपी अनुरोधों के साथ काम करने वाला सबसे आसान डीओएम स्ट्रक्चर बनाया है.

<!-- Basic HTML Setup -->
<div class="illo color-mixed">
  <div class="illo color-primary"></div>
  <div class="illo color-secondary"></div>
</div>

सीएसएस: स्टाइल के साथ आसान स्ट्रक्चर

हमने हर क्षेत्र को सही जगह पर रखने के लिए, एब्सोलूट पोज़िशनिंग का इस्तेमाल किया. साथ ही, हर क्षेत्र में बैकग्राउंड इलस्ट्रेशन को अलाइन करने के लिए, background-position प्रॉपर्टी में बदलाव किया. इससे सभी क्षेत्र (जिनमें से हर एक में एक ही बैकग्राउंड इमेज है) एक ही एलिमेंट की तरह दिखते हैं.

.illo {
  background: url('../img/illo.png') no-repeat;
  top:        0;
  cursor:     pointer;
}
  .illo.color-primary, .illo.color-secondary {
    position: absolute;
    height:   100%;
  }
  .illo.color-primary {
    width:                350px;
    left:                 0;
    background-position:  top left;
  }
  .illo.color-secondary {
    width:                355px;
    right:                0;
    background-position:  top right;
  }

जीपीयू की मदद से तेज़ी से होने वाले ट्रांज़िशन लागू किए गए, जो रंग बदलने वाले इवेंट को सुनते हैं. हमने .color-mixed पर ईज़िंग में बदलाव किया और इसकी अवधि बढ़ा दी, ताकि यह लगे कि रंगों को आपस में मिक्स होने में समय लगा.

/* Apply Transitions To Backgrounds */
.color-primary, .color-secondary {
  -webkit-transition: background .5s linear;
  -moz-transition:    background .5s linear;
  -ms-transition:     background .5s linear;
  -o-transition:      background .5s linear;
}

.color-mixed {
  position:           relative;
  width:              750px;
  height:             600px;
  -webkit-transition: background 1.5s cubic-bezier(.78,0,.53,1);
  -moz-transition:    background 1.5s cubic-bezier(.78,0,.53,1);
  -ms-transition:     background 1.5s cubic-bezier(.78,0,.53,1);
  -o-transition:      background 1.5s cubic-bezier(.78,0,.53,1);
}

मौजूदा ब्राउज़र के साथ काम करने और सीएसएस3 ट्रांज़िशन के लिए सुझाए गए इस्तेमाल के बारे में जानने के लिए, HTML5please पर जाएं.

JavaScript: इसे काम करना

डाइनैमिक तरीके से रंग असाइन करना आसान है. हम DOM में अपनी कलर क्लास वाले किसी भी एलिमेंट को खोजते हैं और उपयोगकर्ता के चुने गए रंग के आधार पर बैकग्राउंड-रंग सेट करते हैं. हम क्लास जोड़कर, DOM में मौजूद किसी भी एलिमेंट पर अपना ट्रांज़िशन इफ़ेक्ट लागू करते हैं. इससे एक ऐसा आर्किटेक्चर बनता है जो आसान, सुविधाजनक, और स्केलेबल होता है.

function createPotion() {

    var primaryColor = $('.picker.color-primary > li.selected').css('background-color');
    var secondaryColor = $('.picker.color-secondary > li.selected').css('background-color');
    console.log(primaryColor, secondaryColor);
    $('.illo.color-primary').css('background-color', primaryColor);
    $('.illo.color-secondary').css('background-color', secondaryColor);

    var mixedColor = mixColors (
            parseColor(primaryColor),
            parseColor(secondaryColor)
    );

    $('.color-mixed').css('background-color', mixedColor);
}

प्राइमरी और सेकंडरी कलर चुनने के बाद, हम उनके मिक्स किए गए कलर की वैल्यू का हिसाब लगाते हैं. साथ ही, उस वैल्यू को सही डीओएम एलिमेंट को असाइन करते हैं.

// take our rgb(x,x,x) value and return an array of numeric values
function parseColor(value) {
    return (
            (value = value.match(/(\d+),\s*(\d+),\s*(\d+)/)))
            ? [value[1], value[2], value[3]]
            : [0,0,0];
}

// blend two rgb arrays into a single value
function mixColors(primary, secondary) {

    var r = Math.round( (primary[0] * .5) + (secondary[0] * .5) );
    var g = Math.round( (primary[1] * .5) + (secondary[1] * .5) );
    var b = Math.round( (primary[2] * .5) + (secondary[2] * .5) );

    return 'rgb('+r+', '+g+', '+b+')';
}

एचटीएमएल/सीएसएस आर्किटेक्चर के लिए इलस्ट्रेशन: तीन रंग बदलने वाले बॉक्स को अलग-अलग लुक देना

हमारा मकसद, मज़ेदार और असली लाइटिंग इफ़ेक्ट बनाना था. यह इफ़ेक्ट, आस-पास के रंग वाले क्षेत्रों में अलग-अलग रंगों को रखने पर भी एक जैसा दिखता है.

24-बिट PNG की मदद से, हमारे एचटीएमएल एलिमेंट का बैकग्राउंड-रंग, इमेज के पारदर्शी हिस्सों में दिखता है.

इमेज ट्रांसपेरेंसी

रंगीन बॉक्स, अलग-अलग रंगों के मिलते-जुलते हिस्सों को अलग-अलग दिखाते हैं. इससे रोशनी के असली इफ़ेक्ट पर असर पड़ता है. यह इलस्ट्रेशन डिज़ाइन करते समय सबसे बड़ी चुनौतियों में से एक थी.

कलर रीजन

इस समस्या को हल करने के लिए, इलस्ट्रेशन को इस तरह से डिज़ाइन किया गया था कि पारदर्शी हिस्सों में रंग वाले हिस्सों के किनारे कभी न दिखें.

क्षेत्र के किनारों को रंगना

बिल्ड के लिए प्लान बनाना ज़रूरी था. डिज़ाइनर, डेवलपर, और इलस्ट्रेटर के बीच प्लानिंग के लिए एक छोटा सेशन आयोजित किया गया. इससे टीम को यह समझने में मदद मिली कि सभी चीज़ों को कैसे बनाया जाए, ताकि एक साथ काम करने पर वे एक साथ काम कर सकें.

लेयर के नाम से सीएसएस बनाने के बारे में जानकारी मिल सकती है. इस बारे में जानने के लिए, Photoshop फ़ाइल का उदाहरण देखें.

क्षेत्र के किनारों को रंगना

Encore

Chrome का इस्तेमाल न करने वाले उपयोगकर्ताओं के लिए, हमने ऐप्लिकेशन के मुख्य हिस्से को एक स्टैटिक इमेज में दिखाने का लक्ष्य रखा है. ग्रिड नोड ही मुख्य हिस्सा बन गया. बैकग्राउंड टाइल से ऐप्लिकेशन के मकसद के बारे में पता चलता है. साथ ही, पानी में दिखने वाले ग्रिड से, ग्रिड के इमर्सिव 3D एनवायरमेंट के बारे में पता चलता है.

क्षेत्र के किनारों को रंग दें.

अगर आपको Technitone के बारे में ज़्यादा जानना है, तो हमारे ब्लॉग पर बने रहें.

बैंड

पढ़ने के लिए धन्यवाद. शायद हम जल्द ही आपके साथ मिलकर कुछ नया करेंगे!