কেস-স্টাডি - ক্রোমের সাথে জ্যাম

আমরা কিভাবে UI রক তৈরি করেছি

ভূমিকা

জ্যাম উইথ ক্রোম হল একটি ওয়েব ভিত্তিক মিউজিক্যাল প্রজেক্ট যা গুগল তৈরি করেছে। Chrome-এর সাথে JAM সারা বিশ্বের লোকেদের ব্রাউজারের ভিতরে রিয়েল টাইমে একটি ব্যান্ড এবং JAM গঠন করতে দেয়৷ DinahMoe Chrome-এর ওয়েব অডিও API দিয়ে যা সম্ভব ছিল তার সীমানা ঠেলে দিয়েছে, উত্তর আমেরিকার টুল- এ আমাদের টিম আপনার কম্পিউটারকে বাজানো, ড্রামিং এবং বাজানোর জন্য ইন্টারফেস তৈরি করেছে যেন এটি একটি বাদ্যযন্ত্র।

Google ক্রিয়েটিভ ল্যাবের সৃজনশীল দিকনির্দেশের সাথে, চিত্রকর রব বেইলি JAM-এর কাছে উপলব্ধ 19টি যন্ত্রের প্রতিটির জন্য জটিল চিত্র তৈরি করেছেন৷ সেগুলি বন্ধ করে, ইন্টারেক্টিভ ডিরেক্টর বেন ট্রিকলব্যাঙ্ক এবং টুলে আমাদের ডিজাইন টিম প্রতিটি যন্ত্রের জন্য একটি সহজ এবং প্রো ইন্টারফেস তৈরি করেছে৷

সম্পূর্ণ জ্যাম মন্তেজ

যেহেতু প্রতিটি ইন্সট্রুমেন্ট দৃশ্যত অনন্য, টুলের টেকনিক্যাল ডিরেক্টর বারটেক ড্রোজডজ এবং আমি পিএনজি ইমেজ, সিএসএস, এসভিজি এবং ক্যানভাস এলিমেন্টের সমন্বয় ব্যবহার করে সেলাই করেছিলাম।

DinahMoe-এর সাউন্ড ইঞ্জিনের সাথে ইন্টারফেস একই রাখার সময় অনেক যন্ত্রকে ইন্টারঅ্যাকশনের বিভিন্ন পদ্ধতি (যেমন ক্লিক, ড্র্যাগ এবং স্ট্রম - সমস্ত জিনিস যা আপনি একটি যন্ত্রের সাথে করার আশা করেন) পরিচালনা করতে হয়েছিল। আমরা দেখতে পেয়েছি যে একটি সুন্দর খেলার অভিজ্ঞতা প্রদান করতে সক্ষম হওয়ার জন্য আমাদের জাভাস্ক্রিপ্টের মাউসআপ এবং মাউসডাউনের চেয়ে আরও বেশি কিছু প্রয়োজন।

এই সমস্ত বৈচিত্র্যের সাথে মোকাবিলা করার জন্য, আমরা একটি "স্টেজ" উপাদান তৈরি করেছি যা খেলার যোগ্য এলাকাকে কভার করে, বিভিন্ন যন্ত্র জুড়ে ক্লিক, ড্র্যাগ এবং স্ট্রাম পরিচালনা করে।

মঞ্চ

স্টেজ হল আমাদের নিয়ামক যা আমরা একটি যন্ত্র জুড়ে ফাংশন সেটআপ করতে ব্যবহার করি। যেমন যন্ত্রের বিভিন্ন অংশ যোগ করা যা ব্যবহারকারীর সাথে যোগাযোগ করবে। যেহেতু আমরা আরও মিথস্ক্রিয়া যোগ করি (যেমন একটি "হিট") আমরা সেগুলিকে স্টেজের প্রোটোটাইপে যুক্ত করতে পারি।

function Stage(el) {

  // Grab the elements from the dom
  this.el = document.getElementById(el);
  this.elOutput = document.getElementById("output-1");

  // Find the position of the stage element
  this.position();

  // Listen for events
  this.listeners();

  return this;
}

Stage.prototype.position = function() {
  // Get the position
};

Stage.prototype.offset = function() {
  // Get the offset of the element in the window
};

Stage.prototype.listeners = function() {
  // Listen for Resizes or Scrolling
  // Listen for Mouse events
};

উপাদান এবং মাউস অবস্থান পাওয়া

আমাদের প্রথম কাজ হল ব্রাউজার উইন্ডোতে মাউস স্থানাঙ্কগুলিকে আমাদের স্টেজ উপাদানের সাথে আপেক্ষিকভাবে অনুবাদ করা। এটি করার জন্য আমাদের পৃষ্ঠায় আমাদের স্টেজ কোথায় আছে তা বিবেচনা করতে হবে।

যেহেতু আমাদের খুঁজে বের করতে হবে যেখানে উপাদানটি সম্পূর্ণ উইন্ডোর সাথে আপেক্ষিক, শুধুমাত্র এর মূল উপাদান নয়, এটি শুধুমাত্র offsetTop এবং offsetLeft উপাদানগুলি দেখার চেয়ে একটু বেশি জটিল। সবচেয়ে সহজ বিকল্প হল getBoundingClientRect ব্যবহার করা, যা উইন্ডোর সাপেক্ষে অবস্থান দেয়, ঠিক মাউস ইভেন্টের মতো এবং এটি নতুন ব্রাউজারে ভালভাবে সমর্থিত

Stage.prototype.offset = function() {
  var _x, _y,
      el = this.el;

  // Check to see if bouding is available
  if (typeof el.getBoundingClientRect !== "undefined") {

    return el.getBoundingClientRect();

  } else {
    _x = 0;
    _y = 0;

    // Go up the chain of parents of the element
    // and add their offsets to the offset of our Stage element

    while (el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
      _x += el.offsetLeft;
      _y += el.offsetTop;
      el = el.offsetParent;
    }

    // Subtract any scrolling movment
    return {top: _y - window.scrollY, left: _x - window.scrollX};
  }
};

যদি getBoundingClientRect বিদ্যমান না থাকে, তাহলে আমাদের কাছে একটি সাধারণ ফাংশন রয়েছে যা কেবলমাত্র অফসেট যোগ করবে, উপাদানের পিতামাতার চেইনটি যতক্ষণ না এটি শরীরে পৌঁছায়। তারপরে আমরা উইন্ডোটির সাথে সম্পর্কিত অবস্থান পেতে উইন্ডোটি কতদূর স্ক্রোল করা হয়েছে তা বিয়োগ করি। আপনি যদি jQuery ব্যবহার করেন তাহলে অফসেট() ফাংশনটি প্ল্যাটফর্ম জুড়ে অবস্থান নির্ণয়ের জটিলতা মোকাবেলায় একটি দুর্দান্ত কাজ করে, তবে আপনাকে এখনও স্ক্রোল করা পরিমাণ বিয়োগ করতে হবে।

যখনই পৃষ্ঠাটি স্ক্রোল করা হয় বা পুনরায় আকার দেওয়া হয় তখনই উপাদানটির অবস্থান পরিবর্তিত হয়েছে। আমরা এই ইভেন্টগুলির জন্য শুনতে এবং অবস্থানের জন্য আবার পরীক্ষা করতে পারি। এই ইভেন্টগুলি একটি সাধারণ স্ক্রোল বা রিসাইজে অনেকবার গুলি করা হয়, তাই একটি বাস্তব অ্যাপ্লিকেশনে আপনি কতবার অবস্থানটি পুনরায় পরীক্ষা করেন তা সীমিত করা সম্ভবত ভাল। এটি করার অনেক উপায় আছে, কিন্তু HTML5 Rocks-এ অনুরোধ অ্যানিমেশনফ্রেম ব্যবহার করে স্ক্রোল ইভেন্টগুলি ডিবাউন্স করার জন্য একটি নিবন্ধ রয়েছে যা এখানে ভাল কাজ করবে।

আমরা কোনো হিট সনাক্তকরণ পরিচালনা করার আগে, যখনই স্টেজ এলাকায় মাউস সরানো হয় তখন এই প্রথম উদাহরণটি আপেক্ষিক x এবং y আউটপুট করবে।

Stage.prototype.listeners = function() {
  var output = document.getElementById("output");

  this.el.addEventListener('mousemove', function(e) {
      // Subtract the elements position from the mouse event's x and y
      var x = e.clientX - _self.positionLeft,
          y = e.clientY - _self.positionTop;

      // Print out the coordinates
      output.innerHTML = (x + "," + y);

  }, false);
};

মাউসের গতিবিধি দেখা শুরু করতে, আমরা একটি নতুন স্টেজ অবজেক্ট তৈরি করব এবং এটিকে ডিভের আইডি দিয়ে দেব যা আমরা আমাদের স্টেজ হিসাবে ব্যবহার করতে চাই।

//-- Create a new Stage object, for a div with id of "stage"
var stage = new Stage("stage");

সহজ হিট সনাক্তকরণ

ক্রোমের সাথে JAM-এ সমস্ত উপকরণ ইন্টারফেস জটিল নয়। আমাদের ড্রাম মেশিন প্যাডগুলি কেবলমাত্র সাধারণ আয়তক্ষেত্র, যাতে একটি ক্লিক তাদের সীমার মধ্যে পড়ে কিনা তা সনাক্ত করা সহজ করে তোলে।

ঢোল যন্ত্র

আয়তক্ষেত্র দিয়ে শুরু করে, আমরা কিছু বেস ধরনের আকার সেটআপ করব। প্রতিটি আকৃতির বস্তুর তার সীমানা জানতে হবে এবং এর মধ্যে একটি বিন্দু আছে কিনা তা পরীক্ষা করার ক্ষমতা থাকতে হবে।

function Rect(x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  return this;
}

Rect.prototype.inside = function(x, y) {
  return x >= this.x && y >= this.y
      && x <= this.x + this.width
      && y <= this.y + this.height;
};

প্রতিটি নতুন আকৃতির ধরন যা আমরা যোগ করি সেটিকে হিট জোন হিসেবে নিবন্ধন করতে আমাদের স্টেজ অবজেক্টের মধ্যে একটি ফাংশনের প্রয়োজন হবে।

Stage.prototype.addRect = function(id) {
  var el = document.getElementById(id),
      rect = new Rect(
        el.offsetLeft,
        el.offsetTop,
        el.offsetWidth,
        el.offsetHeight
      );

  rect.el = el;

  this.hitZones.push(rect);
  return rect;
};

মাউস ইভেন্টে, প্রতিটি আকৃতির উদাহরণ পরীক্ষা করে দেখবে যে পাস করা মাউস x এবং y এটির জন্য একটি হিট এবং সত্য বা মিথ্যা ফেরত দেয়।

আমরা স্টেজ এলিমেন্টে একটি "সক্রিয়" শ্রেণীও যোগ করতে পারি যা মাউস কার্সারকে স্কোয়ারের উপরে ঘুরানোর সময় একটি পয়েন্টার হিসেবে পরিবর্তন করবে।

this.el.addEventListener ('mousemove', function(e) {
  var x = e.clientX - _self.positionLeft,
      y = e.clientY - _self.positionTop;

  _self.hitZones.forEach (function(zone){
    if (zone.inside(x, y)) {
      // Add class to change colors
      zone.el.classList.add('hit');
      // change cursor to pointer
      this.el.classList.add('active');
    } else {
      zone.el.classList.remove('hit');
      this.el.classList.remove('active');
    }
  });

}, false);

আরও আকার

আকারগুলি আরও জটিল হওয়ার সাথে সাথে তাদের ভিতরে একটি বিন্দু আছে কিনা তা খুঁজে বের করার গণিত আরও জটিল হয়ে ওঠে। যাইহোক, এই সমীকরণগুলি ভালভাবে প্রতিষ্ঠিত এবং অনলাইনে অনেক জায়গায় বিশদভাবে নথিভুক্ত করা হয়েছে। আমি দেখেছি সেরা কিছু জাভাস্ক্রিপ্ট উদাহরণ কেভিন লিন্ডসে এর জ্যামিতি লাইব্রেরি থেকে।

সৌভাগ্যবশত Chrome এর সাথে JAM তৈরি করার সময় আমাদেরকে কখনোই বৃত্ত এবং আয়তক্ষেত্রের বাইরে যেতে হয়নি, কোনো অতিরিক্ত জটিলতা সামলানোর জন্য আকৃতি এবং স্তরের সমন্বয়ের উপর নির্ভর করে।

ড্রাম আকার

চেনাশোনা

একটি বিন্দু একটি বৃত্তাকার ড্রামের মধ্যে আছে কিনা তা পরীক্ষা করার জন্য আমাদের একটি বৃত্তের ভিত্তি আকৃতি তৈরি করতে হবে। যদিও এটি আয়তক্ষেত্রের সাথে বেশ সাদৃশ্যপূর্ণ, তবে সীমা নির্ধারণ এবং বিন্দুটি বৃত্তের ভিতরে আছে কিনা তা পরীক্ষা করার জন্য এটির নিজস্ব পদ্ধতি থাকবে।

function Circle(x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  return this;
}

Circle.prototype.inside = function(x, y) {
  var dx = x - this.x,
      dy = y - this.y,
      r = this.radius;
  return dx * dx + dy * dy <= r * r;
};

রঙ পরিবর্তন করার পরিবর্তে, হিট ক্লাস যোগ করা একটি CSS3 অ্যানিমেশন ট্রিগার করবে। পটভূমির আকার আমাদেরকে ড্রামের অবস্থানকে প্রভাবিত না করে দ্রুত চিত্রকে স্কেল করার একটি চমৎকার উপায় দেয়। তাদের সাথে এই কাজের জন্য আপনাকে অন্যান্য ব্রাউজারের উপসর্গ যোগ করতে হবে (-moz, -o এবং -ms) এবং একটি আন-প্রিফিক্সড সংস্করণও যোগ করতে চাইতে পারে।

#snare.hit{
  { % mixin animation: drumHit .15s linear infinite; % }
}

@{ % mixin keyframes drumHit % } {
  0%   { background-size: 100%;}
  10%  { background-size: 95%; }
  30%  { background-size: 97%; }
  50%  { background-size: 100%;}
  60%  { background-size: 98%; }
  70%  { background-size: 100%;}
  80%  { background-size: 99%; }
  100% { background-size: 100%;}
}

স্ট্রিংস

আমাদের গিটারস্ট্রিং ফাংশন একটি ক্যানভাস আইডি এবং রেক্ট অবজেক্ট নেবে এবং সেই আয়তক্ষেত্রের কেন্দ্রে একটি রেখা আঁকবে।

function GuitarString(rect) {
  this.x = rect.x;
  this.y = rect.y + rect.height / 2;
  this.width = rect.width;
  this._strumForce = 0;
  this.a = 0;
}

যখন আমরা এটিকে কম্পিত করতে চাই, তখন আমরা স্ট্রিংটিকে গতিশীল করতে আমাদের স্ট্রাম ফাংশনকে কল করব। আমরা যে ফ্রেমটি রেন্ডার করি তা কিছুটা কমিয়ে দেবে এবং একটি কাউন্টার বাড়াবে যা স্ট্রিংটিকে সামনে পিছনে দোলাবে।

GuitarString.prototype.strum = function() {
  this._strumForce = 5;
};

GuitarString.prototype.render = function(ctx, canvas) {
  ctx.strokeStyle = "#000000";
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(this.x, this.y);
  ctx.bezierCurveTo(
      this.x, this.y + Math.sin(this.a) * this._strumForce,
      this.x + this.width, this.y + Math.sin(this.a) * this._strumForce,
      this.x + this.width, this.y);
  ctx.stroke();

  this._strumForce *= 0.99;
  this.a += 0.5;
};

ছেদ এবং স্ট্রামিং

স্ট্রিং জন্য আমাদের আঘাত এলাকা শুধু আবার একটি বাক্স হতে যাচ্ছে. সেই বাক্সের মধ্যে ক্লিক করলে স্ট্রিং অ্যানিমেশন ট্রিগার করা উচিত। কিন্তু কে একটি গিটার ক্লিক করতে চায়?

স্ট্রমিং যোগ করার জন্য আমাদের স্ট্রিং বাক্সের ছেদ এবং ব্যবহারকারীর মাউস যে লাইনটি ভ্রমণ করছে তা পরীক্ষা করতে হবে।

মাউসের পূর্ববর্তী এবং বর্তমান অবস্থানের মধ্যে পর্যাপ্ত দূরত্ব পেতে, আমরা যে হারে মাউস সরানোর ইভেন্টগুলি পাই তার গতি কমাতে হবে। এই উদাহরণের জন্য, আমরা 50 মিলিসেকেন্ডের জন্য মাউসমুভ ইভেন্টগুলিকে উপেক্ষা করার জন্য একটি পতাকা সেট করব।

document.addEventListener('mousemove', function(e) {
  var x, y;

  if (!this.dragging || this.limit) return;

  this.limit = true;

  this.hitZones.forEach(function(zone) {
    this.checkIntercept(
      this.prev[0],
      this.prev[1],
      x,
      y,
      zone
    );
  });

  this.prev = [x, y];

  setInterval(function() {
    this.limit = false;
  }, 50);
};

পরবর্তীতে আমাদেরকে কিছু ইন্টারসেকশন কোডের উপর নির্ভর করতে হবে যা কেভিন লিন্ডসে লিখেছিলেন যেটি দেখতে মাউস চলাচলের লাইন আমাদের আয়তক্ষেত্রের মাঝখানে ছেদ করে কিনা।

Rect.prototype.intersectLine = function(a1, a2, b1, b2) {
  //-- http://www.kevlindev.com/gui/math/intersection/Intersection.js
  var result,
      ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
      ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
      u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);

  if (u_b != 0) {
    var ua = ua_t / u_b;
    var ub = ub_t / u_b;

    if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
      result = true;
    } else {
      result = false; //-- No Intersection
    }
  } else {
    if (ua_t == 0 || ub_t == 0) {
      result = false; //-- Coincident
    } else {
      result = false; //-- Parallel
    }
  }

  return result;
};

অবশেষে আমরা একটি স্ট্রিং ইন্সট্রুমেন্ট তৈরি করতে একটি নতুন ফাংশন যোগ করব। এটি নতুন স্টেজ তৈরি করবে, বেশ কয়েকটি স্ট্রিং সেট আপ করবে এবং ক্যানভাসের প্রসঙ্গ পাবে যা আঁকা হবে।

function StringInstrument(stageID, canvasID, stringNum){
  this.strings = [];
  this.canvas = document.getElementById(canvasID);
  this.stage = new Stage(stageID);
  this.ctx = this.canvas.getContext('2d');
  this.stringNum = stringNum;

  this.create();
  this.render();

  return this;
}

এরপরে আমরা স্ট্রিংগুলির হিট অঞ্চলগুলিকে অবস্থান করব এবং তারপরে সেগুলিকে স্টেজ এলিমেন্টে যুক্ত করব।

StringInstrument.prototype.create = function() {
  for (var i = 0; i < this.stringNum; i++) {
    var srect = new Rect(10, 90 + i * 15, 380, 5);
    var s = new GuitarString(srect);
    this.stage.addString(srect, s);
    this.strings.push(s);
  }
};

অবশেষে স্ট্রিংইনস্ট্রুমেন্টের রেন্ডার ফাংশনটি আমাদের সমস্ত স্ট্রিং লুপ করবে এবং তাদের রেন্ডার পদ্ধতিগুলিকে কল করবে। এটি সব সময় চলে, অনুরোধ অ্যানিমেশনফ্রেম উপযুক্ত দেখে। আপনি স্মার্ট অ্যানিমেশনের জন্য পল আইরিশের প্রবন্ধ requestAnimationFrame- এ requestAnimationFrame সম্পর্কে আরও পড়তে পারেন।

একটি বাস্তব অ্যাপ্লিকেশনে আপনি একটি পতাকা সেট করতে চাইতে পারেন যখন একটি নতুন ক্যানভাস ফ্রেম আঁকা বন্ধ করার জন্য কোনো অ্যানিমেশন ঘটছে না।

StringInstrument.prototype.render = function() {
  var _self = this;

  requestAnimFrame(function(){
    _self.render();
  });

  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

  for (var i = 0; i < this.stringNum; i++) {
    this.strings[i].render(this.ctx);
  }
};

শেষ করি

আমাদের সমস্ত মিথস্ক্রিয়া পরিচালনা করার জন্য একটি সাধারণ স্টেজ উপাদান থাকা তার ত্রুটিগুলি ছাড়া নয়। এটি গণনাগতভাবে আরও জটিল, এবং কার্সার পয়েন্টার ইভেন্টগুলি পরিবর্তন করার জন্য অতিরিক্ত কোড যোগ না করেই সীমাবদ্ধ। যাইহোক, Chrome এর সাথে JAM-এর জন্য, পৃথক উপাদানগুলি থেকে দূরে মাউস ইভেন্টগুলিকে বিমূর্ত করতে সক্ষম হওয়ার সুবিধাগুলি সত্যিই ভাল কাজ করেছে। এটি আমাদের ইন্টারফেস ডিজাইনের সাথে আরও পরীক্ষা করতে, অ্যানিমেটিং উপাদানগুলির পদ্ধতিগুলির মধ্যে স্যুইচ করতে, মৌলিক আকারের চিত্রগুলি প্রতিস্থাপন করতে SVG ব্যবহার করতে, হিট এলাকাগুলিকে সহজেই অক্ষম করতে এবং আরও অনেক কিছু।

ড্রামস এবং স্টিংগুলি কার্যকর দেখতে আপনার নিজস্ব JAM শুরু করুন এবং স্ট্যান্ডার্ড ড্রাম বা ক্লাসিক ক্লিন ইলেকট্রিক গিটার নির্বাচন করুন।

জ্যাম লোগো