กรณีศึกษา - การสร้าง Technitone.com

Technitone ประสบการณ์เสียงบนเว็บ

Technitone.com เป็นการผสมผสานระหว่าง WebGL, Canvas, Web Sockets, CSS3, JavaScript, Flash และ Web Audio API ใหม่ใน Chrome

บทความนี้จะอธิบายถึงแง่มุมต่างๆ ของการผลิต ไม่ว่าจะเป็นแผน เซิร์ฟเวอร์ เสียง ภาพ และเวิร์กโฟลว์บางส่วนที่เราใช้ประโยชน์จากการออกแบบสำหรับการโต้ตอบ ส่วนส่วนใหญ่จะมีข้อมูลโค้ด การสาธิต และการดาวน์โหลด ที่ท้ายบทความ จะมีลิงก์ดาวน์โหลดที่คุณสามารถดึงข้อมูลทั้งหมดมาเป็นไฟล์ ZIP ไฟล์เดียว

ทีมผลิตของ gskenter.com

คอนเสิร์ต

เราไม่ใช่วิศวกรเสียงที่ gskenter.com แน่นอน แต่เราต้องท้าทายตัวเองด้วยความท้าทายเพื่อให้เราค้นหาแผนการ

  • ผู้ใช้วางผังเสียงในตารางกริด "ได้แรงบันดาลใจ" จาก ToneMatrix ของ Andre
  • โทนเสียงจะใช้กับเครื่องดนตรีต้นแบบ ชุดกลอง หรือแม้แต่การบันทึกเสียงของผู้ใช้เอง
  • ผู้ใช้ที่เชื่อมต่อหลายคนจะเล่นในตารางกริดเดียวกัน
  • ...หรือเข้าสู่โหมดฉายเดี่ยวเพื่อสำรวจด้วยตัวเอง
  • เซสชันการเชิญให้ผู้ใช้สามารถจัดวงดนตรีและร่วมแสดงดนตรีได้แบบกะทันหัน

เราเปิดโอกาสให้ผู้ใช้ได้สำรวจ Web Audio API โดยใช้แผงเครื่องมือที่ใช้ตัวกรองและเอฟเฟกต์เสียงกับโทนเสียง

Technitone โดย gskaking.com

นอกจากนี้ เรายังดำเนินการต่อไปนี้

  • จัดเก็บองค์ประกอบและเอฟเฟกต์ของผู้ใช้ไว้เป็นข้อมูลและซิงค์กับไคลเอ็นต์ต่างๆ
  • แสดงตัวเลือกสีเพื่อให้วาดเพลงที่ดูดี
  • มีแกลเลอรีเพื่อให้ผู้คนสามารถรับฟัง ให้ความรัก หรือแม้แต่แก้ไขผลงานของผู้อื่น

เรายึดเอาคำอุปมาอุปไมยกริดที่คุ้นเคยมาใช้ในอวกาศ 3 มิติ เพิ่มแสง พื้นผิว และเอฟเฟกต์อนุภาค เก็บไว้ในแบบ CSS ที่ยืดหยุ่น (หรือเต็มหน้าจอ) และอินเทอร์เฟซที่ขับเคลื่อนด้วย JS

ทริปขับรถ

เครื่องมือ เอฟเฟกต์ และข้อมูลตารางกริดจะรวมและแยกเป็นอนุกรมบนไคลเอ็นต์ จากนั้นจะส่งไปยังแบ็กเอนด์ Node.js ที่กำหนดเองเพื่อแก้ปัญหาสำหรับผู้ใช้หลายราย à la Socket.io ข้อมูลนี้จะถูกส่งกลับไปยังไคลเอ็นต์โดยที่ผู้เล่นแต่ละคนมีส่วนร่วมด้วย ก่อนที่จะกระจายไปยังเลเยอร์ CSS, WebGL และ WebAudio ที่เกี่ยวข้องเพื่อแสดงผล UI, ตัวอย่าง และเอฟเฟกต์ระหว่างการเล่นแบบผู้ใช้หลายคน

การสื่อสารแบบเรียลไทม์กับซ็อกเก็ตจะฟีด JavaScript บนไคลเอ็นต์และ JavaScript บนเซิร์ฟเวอร์

แผนภาพเซิร์ฟเวอร์ Technitone

เราใช้โหนดกับเซิร์ฟเวอร์ในทุกๆ ด้าน ซึ่งเป็นเว็บเซิร์ฟเวอร์แบบคงที่และซ็อกเก็ตเซิร์ฟเวอร์ของเราในที่เดียว Express คือสิ่งที่เราใช้ โดยเป็นเว็บเซิร์ฟเวอร์เต็มรูปแบบที่สร้างขึ้นใน Node ทั้งยังสามารถปรับขนาดได้อย่างมาก ปรับแต่งได้ในระดับสูง และจัดการด้านเซิร์ฟเวอร์ในระดับต่ำสำหรับคุณ (เหมือนกับที่ Apache หรือ Windows Server ทำได้) ในฐานะนักพัฒนาซอฟต์แวร์ คุณเพียงแค่ต้องให้ความสำคัญกับการสร้างแอปพลิเคชันเท่านั้น

การสาธิตสำหรับผู้ใช้หลายคน (ภาพที่เห็นเป็นเพียงภาพหน้าจอเท่านั้น)

การสาธิตนี้ต้องใช้เซิร์ฟเวอร์โหนด และเนื่องจากบทความนี้ไม่ใช่ต้นแบบ เราได้รวมภาพหน้าจอของการสาธิตลักษณะที่ปรากฏหลังจากที่คุณติดตั้ง Node.js กำหนดค่าเว็บเซิร์ฟเวอร์ และเรียกใช้ในเครื่อง ทุกครั้งที่ผู้ใช้ใหม่เข้าชมการติดตั้งการสาธิต ระบบจะเพิ่มตารางกริดใหม่และแสดงผลงานของทุกคนให้กันและกัน

ภาพหน้าจอของการสาธิต Node.js

โหนดใช้งานง่าย การใช้ทั้ง Socket.io และคำขอ POST ที่กำหนดเองทำให้ไม่ต้องสร้างกิจวัตรที่ซับซ้อนสำหรับการซิงค์ Socket.io จัดการกับเรื่องนี้อย่างโปร่งใส โดย JSON จะได้รับการส่งต่อ

ง่ายแค่ไหน ดูนี่

ด้วย JavaScript 3 บรรทัด เราจึงมีเว็บเซิร์ฟเวอร์ที่พร้อมทำงานร่วมกับ 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 ของเราเป็นเรื่องของการจัดกำหนดการ แทนที่จะตั้งช่วงตัวจับเวลาให้เท่ากับจังหวะเวลาประมวลผลเสียงทุกบีต เราก็มีช่วงระยะเวลาที่น้อยลงเพื่อจัดการและกำหนดเวลาส่งเสียงในคิว วิธีนี้ช่วยให้ API ทำงานล่วงหน้าในการแก้ไขข้อมูลเสียง รวมถึงประมวลผลตัวกรองและเอฟเฟกต์ก่อนที่เราจะทำให้ CPU ส่งเสียงได้จริง เมื่อถึงจังหวะนั้น อุปกรณ์ก็มีข้อมูลทั้งหมดที่จำเป็นต่อการนำเสนอผลลัพธ์สุทธิต่อลำโพงแล้ว

โดยรวมแล้ว ทุกสิ่งที่จำเป็นในการเพิ่มประสิทธิภาพ เมื่อเราผลักดัน CPU ให้หนักเกินไป ระบบจึงข้ามกระบวนการต่างๆ (ป๊อปอัป คลิก ขีดข่วน) เพื่อให้ทันกับกำหนดการนั้น และทุ่มเทอย่างหนักเพื่อหยุดยั้งความบ้าคลั่งทั้งหมดนี้หากคุณข้ามไปยังแท็บอื่นใน Chrome

การแสดงแสงสี

ด้านหน้าและตรงกลางคืออุโมงค์ตารางกริดและอนุภาคของเรา นี่คือเลเยอร์ WebGL ของ Technitone

WebGL มอบประสิทธิภาพที่เหนือกว่าวิธีการอื่นๆ ส่วนใหญ่ในการแสดงภาพบนเว็บด้วยการมอบหมายให้ GPU ทำงานร่วมกับโปรเซสเซอร์ ประสิทธิภาพที่เพิ่มขึ้นนี้มาพร้อมกับต้นทุนของการพัฒนาที่เกี่ยวข้องมากขึ้นอย่างมากและเส้นโค้งการเรียนรู้ที่ชันขึ้นมาก อย่างไรก็ตาม หากคุณหลงใหลในการโต้ตอบบนเว็บอย่างแท้จริง และต้องการข้อจำกัดด้านประสิทธิภาพให้น้อยที่สุดเท่าที่จะทำได้ WebGL จึงนำเสนอโซลูชันที่เทียบได้กับ Flash

การสาธิต WebGL

เนื้อหา WebGL จะแสดงผลใน Canvas (เรียกจริงๆ ว่า HTML5 Canvas) และประกอบด้วยองค์ประกอบพื้นฐานหลักต่อไปนี้

  • จุดยอดของวัตถุ (เรขาคณิต)
  • เมทริกซ์ตำแหน่ง (พิกัด 3 มิติ)
    • ตัวปรับแสงเงา (คำอธิบายเกี่ยวกับลักษณะเรขาคณิต ซึ่งเชื่อมโยงกับ GPU โดยตรง)
    • บริบท ("ทางลัด" ไปยังองค์ประกอบที่ GPU อ้างอิง)
    • บัฟเฟอร์ (ไปป์ไลน์สำหรับการส่งข้อมูลบริบทไปยัง GPU)
    • โค้ดหลัก (ตรรกะทางธุรกิจที่เจาะจงสำหรับการโต้ตอบที่ต้องการ)
    • เมธอด"draw" (เปิดใช้งานตัวให้เฉดสีและวาดพิกเซลบนผืนผ้าใบ)

ขั้นตอนพื้นฐานในการแสดงผลเนื้อหา WebGL ไปบนหน้าจอมีลักษณะดังนี้

  1. กำหนดเมทริกซ์มุมมอง (ปรับการตั้งค่าสำหรับกล้องที่มองไปสู่พื้นที่ 3 มิติ โดยกำหนดระนาบภาพ)
  2. กำหนดเมทริกซ์ตำแหน่ง (ประกาศต้นทางในพิกัด 3 มิติที่มีการวัดตำแหน่งสัมพัทธ์)
  3. เติมบัฟเฟอร์ด้วยข้อมูล (ตำแหน่งจุดยอด, สี, พื้นผิว...) เพื่อส่งผ่านไปยังบริบทผ่านตัวให้เฉดสี
  4. แยกและจัดระเบียบข้อมูลจากบัฟเฟอร์ด้วยตัวปรับแสงเงาและส่งไปยัง GPU
  5. เรียกใช้วิธีการวาดเพื่อบอกบริบทเพื่อเปิดใช้งานตัวปรับเฉดสี เรียกใช้ข้อมูล และอัปเดต Canvas

ซึ่งมีลักษณะดังนี้

กำหนดเมทริกซ์มุมมอง...

// 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 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);

โปรดล้างผืนผ้าใบให้ทุกเฟรมหากไม่ต้องการให้ภาพที่ถ่ายในเวอร์ชันอัลฟ่าซ้อนทับกัน

The Venue

นอกจากตารางกริดและอุโมงค์อนุภาคแล้ว องค์ประกอบ UI อื่นๆ ทั้งหมดยังสร้างขึ้นใน HTML / CSS และตรรกะแบบอินเทอร์แอกทีฟใน JavaScript

ตั้งแต่เริ่มต้น เราตัดสินใจว่าผู้ใช้ควรโต้ตอบกับตารางกริดให้เร็วที่สุด ไม่มีหน้าจอแนะนำ ไม่มีวิธีการ ไม่มีบทแนะนำ มีแค่ "ไป" หากอินเทอร์เฟซโหลดขึ้น ก็จะไม่ต้องทำอะไรที่ทำให้อินเทอร์เฟซทำงานช้าลง

ด้วยเหตุนี้ เราจึงต้องพิจารณาอย่างรอบคอบถึงวิธีแนะนำผู้ที่ใช้งานครั้งแรกตลอดการโต้ตอบ เราได้รวมคำแนะนำเล็กๆ น้อยๆ เช่น การเปลี่ยนคุณสมบัติของเคอร์เซอร์ CSS ตามตำแหน่งเมาส์ของผู้ใช้ภายในพื้นที่ WebGL หากเคอร์เซอร์อยู่บนเส้นตาราง เราจะสลับไปเป็นเคอร์เซอร์รูปมือ (เนื่องจากสามารถโต้ตอบได้โดยการพล็อตโทน) หากเส้นตารางวางอยู่ในช่องว่างรอบตารางกริด เราจะสลับไอคอนนั้นมาเป็นเคอร์เซอร์กากบาทในทิศทาง (เพื่อระบุว่าสามารถหมุนหรือระเบิดตารางออกเป็นเลเยอร์ต่างๆ ได้)

เตรียมตัวให้พร้อมสำหรับการแสดง

ลด (โปรเซสเซอร์ก่อนหน้าสำหรับ CSS) และ CodeKit (การพัฒนาเว็บบนสเตียรอยด์) ช่วยลดเวลาที่ใช้ในการแปลไฟล์การออกแบบเป็น HTML/CSS ที่ตัดออกมาได้ เครื่องมือเหล่านี้ช่วยให้เราสามารถจัดระเบียบ เขียน และเพิ่มประสิทธิภาพ CSS ได้หลากหลายยิ่งขึ้น โดยใช้ประโยชน์จากตัวแปร มิกซ์ (ฟังก์ชัน) และแม้แต่การคำนวณทางคณิตศาสตร์

เอฟเฟกต์เวที

เราได้สร้างเอฟเฟกต์ง่ายๆ ที่ช่วยให้แอปพลิเคชันดูมีชีวิตชีวาขึ้นมาและมอบคิวภาพให้แก่ผู้ใช้ที่ระบุว่ากำลังใช้เครื่องดนตรีใดอยู่เมื่อใช้การเปลี่ยน CSS3 และ backbone.js

สีของ Technitone

Backbone.js ช่วยให้เราตรวจจับเหตุการณ์การเปลี่ยนสีและนำสีใหม่ไปใช้กับองค์ประกอบ DOM ที่เหมาะสมได้ การเปลี่ยน CSS3 ที่ใช้ GPU เป็นตัวเร่งจะจัดการกับการเปลี่ยนแปลงรูปแบบสีโดยมีผลกระทบต่อประสิทธิภาพน้อยมากหรือไม่มีเลย

การเปลี่ยนสีส่วนใหญ่บนองค์ประกอบอินเทอร์เฟซเกิดจากการเปลี่ยนสีพื้นหลัง นอกเหนือจากสีพื้นหลังนี้ เราจะวางภาพพื้นหลังที่มีพื้นที่โปร่งใสเชิงกลยุทธ์เพื่อให้สีพื้นหลังโดดเด่น

HTML: พื้นฐาน

เราต้องการพื้นที่สี 3 ส่วนในการสาธิต ได้แก่ พื้นที่สีที่ผู้ใช้เลือก 2 ราย และภูมิภาคสีผสมลำดับที่ 3 เราสร้างโครงสร้าง DOM ที่เรียบง่ายที่สุดซึ่งรองรับการเปลี่ยน CSS3 และคำขอ HTTP ที่น้อยที่สุดสำหรับภาพประกอบของเรา

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

CSS: โครงสร้างเรียบง่ายมีสไตล์

เราใช้การจัดตำแหน่งแบบสัมบูรณ์เพื่อวางแต่ละภูมิภาคในตำแหน่งที่ถูกต้อง และปรับคุณสมบัติตำแหน่งพื้นหลังเพื่อปรับแนวภาพประกอบพื้นหลังภายในแต่ละภูมิภาค ซึ่งจะทำให้ทุกภูมิภาค (แต่ละภูมิภาคมีภาพพื้นหลังเดียวกัน) ดูเหมือนองค์ประกอบเดียว

.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;
  }

ใช้การเปลี่ยนแบบเร่งด้วย GPU ที่รอรับเหตุการณ์การเปลี่ยนสี เราเพิ่มระยะเวลาและแก้ไขการค่อยๆ เปลี่ยนใน .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);
}

ไปที่ HTML5โปรดสำหรับการสนับสนุนเบราว์เซอร์ในปัจจุบันและการใช้งานที่แนะนำสำหรับการเปลี่ยน CSS3

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);
}

เมื่อเลือกสีหลักและสีรองแล้ว เราจะคำนวณค่าสีผสมและกำหนดค่าที่ได้ให้กับองค์ประกอบ DOM ที่เหมาะสม

// 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+')';
}

ภาพประกอบสำหรับสถาปัตยกรรม HTML/CSS: นำเสนอบุคลิกภาพกล่องเปลี่ยนสี 3 สี

เป้าหมายของเราคือการสร้างเอฟเฟกต์แสงที่สนุกและสมจริงซึ่งคงความสมบูรณ์ของภาพไว้เมื่อนำสีที่ตัดกันมาวางในพื้นที่สีที่อยู่ติดกัน

PNG 24 บิต ช่วยให้สีพื้นหลังขององค์ประกอบ HTML สามารถแสดงผ่านพื้นที่โปร่งใสของรูปภาพ

ความโปร่งใสของภาพ

กล่องสีจะสร้างขอบที่แข็งซึ่งสีต่างๆ มาบรรจบกัน วิธีนี้ทำให้เกิดการใช้เอฟเฟกต์การจัดแสงที่สมจริงและเป็นความท้าทายที่ยิ่งใหญ่อย่างหนึ่งในการออกแบบภาพ

พื้นที่สี

วิธีแก้ปัญหาคือการออกแบบภาพประกอบให้ไม่ทำให้ขอบของภูมิภาคสีแสดงผ่านพื้นที่โปร่งใส

ขอบเขตสี

การวางแผนงานสร้างเป็นสิ่งสำคัญ เซสชันการวางแผนอย่างรวดเร็วระหว่างนักออกแบบ นักพัฒนาซอฟต์แวร์ และนักวาดภาพประกอบช่วยให้ทีมเข้าใจวิธีการสร้างทุกสิ่งที่ต้องใช้เพื่อให้ทำงานร่วมกันเมื่อประกอบขึ้นได้

ดูไฟล์ Photoshop เป็นตัวอย่างว่าการตั้งชื่อเลเยอร์สามารถสื่อสารข้อมูลเกี่ยวกับการสร้าง CSS ได้อย่างไร

ขอบเขตสี

อองคอร์

สำหรับผู้ใช้ที่ไม่มี Chrome เราได้ตั้งเป้าหมายที่จะกลั่นเนื้อหาสำคัญของแอปพลิเคชันให้กลายเป็นภาพนิ่งเพียงภาพเดียว โหนดตารางกริดกลายเป็นหลัก ชิ้นส่วนพื้นหลังจะพาดพิงถึงวัตถุประสงค์ของแอปพลิเคชัน และมุมมองที่แสดงในคำแนะนำการสะท้อนสภาพแวดล้อม 3 มิติแบบสมจริงของตารางกริด

ขอบเขตสี

หากสนใจดูข้อมูลเพิ่มเติมเกี่ยวกับ Technitone โปรดติดตามบล็อกของเรา

วง

ขอขอบคุณที่อ่าน บางทีเราจะมาร่วมแจมกับคุณในเร็วๆ นี้