img

ساخت یک Etch A Sketch دیجیتال

/
/
/

اِچ اِ اِسکِچ (Etch A Sketch) ابزار رسم مکانیکی است که توسط یک شخص فرانسوی به نام André Cassagnes اختراع شد، این اسباب‌بازی یک صفحه نمایش صاف خاکستری‌رنگ و دو دستگیره روی قاب خود دارد، با چرخاندن دستگیره‌ها، سوزن درون قاب حرکت می‌کند و پودر آلومینیوم را جابجا می‌کند و خطوط سیاه‌رنگ روی صفحه ترسیم می‌شوند. این ابزار یکی از مشهورترین و پرفروش‌ترین اسباب‌بازی‌های قرن بیستم بود.

 

یاد بگیرید که چگونه با استفاده از canvas، touch events، و device motion یک اسباب بازی محبوب را برای مرورگر دوباره سازی کنید.
ترسیم یکسره (رسم بدون برداشتن قلم) هنری است که سابقه آن به قرون گذشته باز می‌گردد، اما یک محبوبیت تازه زمانی ایجاد شد که اسباب بازی مشهور و محبوب Etch A Sketch در دهه شصت میلادی معرفی شد، وسیله ای که هنوز که هنوز است از محبوبیت برخوردار است.
بیش از یک اسباب بازی، این وسیله به واسطه ای محبوب برای هنرمندان تبدیل شد، که آثاری مثل مونا لیزا و تاج محل را به صورت مجموعه ای از خط‌های تیره بازآفرینی کردند. این وسیله شاهکارهای کوتاه عمری می‌سازد که می‌توانند با چند تکان نابود شوند. ریزه کاری که در این نقاشی‌ها دخیل است برای افرادی همچون ما که به این پدیده به چشم سرگرمی نگاه می‌کنیم حیرت آور می‌باشد.
حتی تصور اینکه بتوان همچین آثاری را خلق کرد برای ما ممکن است ملال آور باشد. در این راهنما، ما این وسیله مکانیکی نقاشی را به عنوان منبع الهام قرار داده و تلاش داریم تا ویژگی هایش را توسط فن آوری وب در دستگاه‌های امروزی اجرا کنیم.
با استفاده ابزاری که به درستی بوم(canvas) نامیده می‌شود، ما ابتدا بر روی تبلت‌ها تمرکز می‌کنیم، که از لحاظ ظاهری شباهت دقیقا مثل اسباب بازی اصلی هستند.
ما می‌توانیم با وقایع لمس touch events برای کنترل دکمه ها، وقایع حرکت دستگاه (device motion events) برای پاک کردن ترسیماتمان استفاده کنیم.
تلفن‌های همراه فراموش نخواهند شد، ما مکاشفه خواهیم کرد که چگونه با استفاده ازWebSockets امکان استفاده جدا سازی کنترل‌ها از فضای ترسیم را پدید آوریم.

۱- دریافت ملزومات
این راهنما از Node.js استفاده خواهد کرد. قبل از آنکه کار را آغاز کنیم، ملزومات (assets) را از FileSilo دانلود کرده و فرمان‌های زیر را اجرا کنید. که پیوست‌های مربوطه را نصب کرده و سرور را اجرا می‌کند. ما از Node برای ایجاد یک localhost استفاده می‌کنیم، و این بعدا نیز برای WebSockets به درد ما خواهد خورد.

npm install
node server/index.js

 

۲- تابع draw()
در ‘main.js’، تابع draw() نقطه مرکزی برنامه ما خواهد بود. ما از بوم(canvas) برای رسم یک خط میان دو نقطه استفاده می‌کنیم، (x1, y1) مبدا می‌باش، جایی که آخرین نقطه در هنگام رسم بوده است، و (x1, x2) مقصد می‌باشد، جایی که ما قصد رسیدن به آن را داریم. اکنون ما نیاز است که تابع را فعال کنیم و چگونگی ترسیم را مشاهده کنیم.

Function draw(x1, y1, x2, y2) {
//context is set globally in init()
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}

 

۳- اجرایی کردن رخ داد‌های صفحه کلید
قبل از اینکه ما دکمه‌ها را تعریف کنیم، بگزارید به سرعت یک مباشر(listener) صفحه کلید را تعریف کنیم که بتواند تابع draw ما را فعال کند. به شما قبلا کد‌های متفاوتی در قالب مثال ارائه شده است، اما لازم است که شما مباشر را کمی دستکاری کنید تا بتواند تابع draw را که ما از پیش نوشته ایم را فعال کند. اکنون مرورگر خود را تازه کنید(refresh) و تماشا کنید که چه چیزی می‌توانید با دکمه‌های جهت خود ترسیم کنید.

Document.addEventListener(‘keydown’,
function(e) {
/*keyCode switch goes here*/
draw(Math.floor(prev_horizontal), Math.floor(prev_vertical), Math.floor(horizontal),
Math.floor(vertical));
prev_vertical = vertical;
prev_horizontal = horizontal;
});

 

۴- تغییر اندازه بوم(canvas)
شاید شما متوجه شده باشید که به المان canvas ما هنوز اندازه ای اختصاص نگرفته است. برای تخته طراحی ما، ما نیاز به فضایی بزرگتر داریم، حتی شاید به کل صفحه احتیاج داشته باشیم. کد زیر از پس رخ داد resize بر می‌آید، اما فراخوانی adjustFrame() در init() را فراموش نکنید.

Function adjustFrame() {
//canvas is defined globally in init()
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
Window.addEventListener(‘resize’,
AdjustFrame);

 

۵- افزودن یک قاب
قصد ما این است که برنامه ما تا حد امکان مشابه اسباب بازی اصلی باشد، بنابراین ما می‌خواهیم که یک قاب را برای اطراف ناحیه ترسیم اضافه کنیم. برای این منظور، ما می‌توانیم یک مقدار برای لبه(margin) تعریف کنیم و CSS را برای #sketch به margin: 20px auto; تغییر داده تا بوم از لحاظ افقی به مرکز برده و فضای بیشتر را در پایین برای دکمه‌ها نگه داریم.

var frameMarginVertical = 122;
var frameMarginHorizontal = 62;
function adjustFrame() {
canvas.width = window.innerWidth – frameMarginHorizontal;
canvas.height = window.innerHeight – frameMarginVertical; }

 

۶- اکنون دکمه‌ها را می‌سازیم
شما می‌توانید دکمه‌ها در

’public/css/styles.css’

بیابید و پیشنهاد می‌شود که به آنها نگاهی داشته باشید. سپس در ذیل <canvas> دو تگ<div> را در فایل HTML را اضافه کنید، به نحوی که در پایین تشریح شده است. مرسوم است که، از دکمه سمت چپ برای رسم افقی، و از سمت راستی برای رسم عمودی استفاده شود. همچنین ما مقادیر جدیدی را به تابع init() ابلاغ می‌کنیم تا برای رویدادای لمس آماده شود.

//HTML
<div id=”dialHorizontal” class=”dial”></div>
<div id=”dialVertical” class=”dial”></div>
//JS
Var targetLeft = document.getElementById(‘dialHorizontal’);
Var regionLeft = new ZingTouch.
Region(targetLeft);
Var targetRight = document.
getElementById(‘dialVertical’);
var regionRight = new ZingTouch.
Region(targetRight);

۷- استفاده از ZingTouch
zingTouch یک کتابخانه ی جاوا اسکریپت می‌باشد که توانایی تشخیص حرکت‌های گوناگونی را دارد، و همچنین رویداد‌های موس را هم رتق و فتق می‌کند. این در فولدر ‘/public/lib/’ برای شما فراهم آمده است و ما از آن برای کنترل دکمه‌ها سود می‌بریم. در پایین پیاده سازی کنترل سمت چپ را شاهد هستید، شما خودتان بایستی برای سمت راست این کد را تغییر و بازنویسی کنید.

regionLeft.bind(targetLeft, ‘rotate’,
function(e) {
if(e.detail.distanceFromLast < 0) {
–horizontal;
} else {
++horizontal;
}
angleHorizontal += e.detail.
distanceFromLast;
targetLeft.style.transform = ‘rotate(‘ +
angleHorizontal + ‘deg)’;
draw(Math.floor(prev_horizontal), Math.
floor(prev_vertical), Math.floor(horizontal),
Math.floor(prev_vertical));
prev_horizontal = horizontal;
));

 

۸- پیاده سازی کران دکمه ها
برای جلوگیری از خروج خطوط از صفحه، ما از تابع canDraw() استفاده می‌کنیم، که یک Boolean بر می‌گرداند. ما به آن جهت را می‌دهیم، که افقی یا عمودی می‌باش، و مقدار که معتغیر vertical یا horizontal می‌باشد. ما این تابع را در مباشر (listener) ‘rotate’ برای هر دو دکمه فراخوانی می‌کینم و اگر تنها اگر درست باشد، ما زاویه را افزایش می‌دهیم و تابع draw() را فراخوانی می‌کنیم.

function canDraw(direction, value) {
var max = (direction === ‘horizontal’)?(canvas.width):(canvas.height);
If(value < 2 || value > max – ۲) {
return false;
}
Return true;
}

 

۹- پیشگیری از مشکلات دکمه‌ها
با کران هایی که همین تازگی ایجاد کردیم، احتمال این وجود دارد که دکمه در یک انتها گیر کند اگر که مقدار از محدوده تجاوز کند، هر چند به مقدار خیلی جزئی. برای پیشگیری از این مسئله، چاره ای باید برای زمانی که canDraw() نادرست(false) می‌شود باندیشیم و مقدار را به آخرین عدد صحیح بازگردانی کنیم، همانگونه که اینجا برای کنترل کننده افقی شاهد هستید:

If(canDraw(‘horizontal’, horizontal)) {
angleHorizontal += e.detail.distanceFromLast;
targetLeft.style.transform = ‘rotate(‘ + angleHorizontal + ‘deg)’;
draw(Math.floor(prev_horizontal), Math.floor(prev_vertical), Math.floor(horizontal), Math.floor(prev_vetical));
prev_horizontal = horizontal;
} else {
Horizontal = prev_horizontal;
}

 

۱۰- امتحان صفحه ترسیم بر روی تبلت
خوب است که همیشه در اولین فرصت برنامه خود را بر روی دستگاهی که هدف نهایی شماست امتحان کنید. برنامه ما اکنون در وضع مناسبی است، و می‌توان نسبت به رویداد‌های لمسی پاسخ گو باشد. مراحل دسترسی از راه دور میزبان محلی را دنبال کنید تا برنامه بر روی تبلت شما بارگذاری گردد.
سپس، ما از safari و منو Develop استفاده خواهیم کرد تا برنامه را بر روی iPad نیز بررسی کنیم. برای دستگاه‌های اندرویدی، از

chrome://inspect

استفاده شود.

 

۱۱- آزمایش شتاب سنج
تبلت خود را با استفاده از USB به رایانه خود متصل کنید و برنامه را با ابزارهای توسعه دهندگان بررسی کنید. با جای گذاری کد زیر، شما بایستی مقادیر گوناگون شتاب را شاهد باشید، وقتی که دستگاه خود را به اطراف حرکت می‌دهید.
به منظور ریست کردن بوم، ما تصمیم گرفتیم که بالاتر ۵ بر روی محور x یک شتاب را در نظر بگیریم، و به آهستگی کدری را کاهش دهیم(eraseRate)

var eraseRate = 1; /*define as a global
variable*/
window.addEventListener(‘devicemotion’, function(event) {
console.log(‘Acceleration::’, event.acceleration);
if(event.acceleration.x > 5) {
eraseRate -= Math.abs(event.acceleration.x/100);
console.log(‘Erase::’, eraseRate);
}
});

 

۱۲- تکان دادن برای پاک کردن
در مراحل قبل ناظر بودیم که چگونه حرکات و شتاب‌ها سنجیده شوند. اکنون ما بایستی هنگامی که شرایط برقرار است fadeDrawing() را فراخوانی کنیم. در این مثال، ما کپی یکسانی از بوم را با تاری متفاوتی رسم می‌کنیم.
در draw() متغیر globalAlpha را به یک بازگردانی کنید و globalCompositeOperation را به source-over بازگردانید.

Function fadeDrawing() {
If (eraseRate < 0) {
context.clearRect(0, 0, cavas.width, cavas.height);
eraseRate = 1;
return;
}
context.globalAlpha = eraseRate;
context.globalAlpha = eraseRate;
context.globalCompositeOperation = ‘copy’;
context.drawImage(canvas, 0, 0);
}

 

۱۳- نزدیک کردن برنامه به نمونه واقعی
تا بدین جای کار، برنامه ما ظاهری ساده و کسالت بار داشته است. به منظور اینکه به برنامه کمی آب و رنگ بدهیم، یک قاب رنگ و سایه درون آن و کمی حجم به دکمه‌ها اضافه می‌کنیم. CSS مورد نیاز برای سایه دکمه‌ها قبلا تهیه شده است، اما شما بایستی آن دو المان را در پایان بدنه(body) اضافه کنید. CSS را براین برای المان‌ها مذکور اینگونه تکمیل کنید:

//HTML
<div id=”dialShadowHorizontal”
class=”shadow”></div>
<div id=”dialShadowVertical”
Class=”shadow”></div>
//CSS
body {
background: #09cbf7;
}
#sketch {
box-shadow: 2px 2px 10px rgba(0, 0, 0, .25)
inset;
}

 

۱۴- معرفی WebSockets
در ابتدای این راهنما، ما به طور خلاصه به استفاده از WebSockets از طریق سرور Node اشاره کردیم. حالا که شما پدی مستقل برای ترسیم بر روی تبلت دارید، نگاهی خواهیم داشت به فراهم کردن آن برای گوشی همراه. با این حال گوشی‌ها ممکن است برای نمایش هم صفحه و هم دکمه زیادی کوچک باشد. بنابراین ما از socket‌ها برای اتصال میان گوشی و صفحه نمایش رایانه سود خواهیم جست.

۱۵- تشخیص اندازه دستگاه
در فایل HTML اصلی، ‘main.js’ را با ‘extra.js’ جایگزین کنید. فایل جدید شامل هر آنچه که تا به حال انجام داده ایم می‌باشد، با دستکاری هایی که برای دستگاه‌های قابل حمل و سوکت ها، که ما آنها را در مراحل بررسی خواهیم کرد. نگاهی داشته باشید بر detectDevice() – این متود اکنون به جای init() در هنگام بارگذاری فراخوانی می‌شود و تصمیم می‌گرد که کدامین مد برای برنامه اتخاذ شود. در زیر مورد به خصوص تشخیص داده شدن گوشی قرار دارد:

if(window.innerWidth < 768) {
socket = io.connect();
document.querySelector(‘#sketch’).remove();
var dials = document.querySelectionAll(‘.dial, .shadow’);
[].forEach.call(dials, function(item) {
Item.classList.add(‘big’);
});
isControls = true;
frameMarginVertical = 62;
socket.emit(‘ready’, {‘ready’: ‘controls’});
}

 

۱۶- از گوشی به رایانه
از میان ‘extra.js’ شما متوجه قطعاتی از کد مثل socket.emit() یا socket.on() خواهید شد. اینها مباشرین(listeners) و مخبرین(emitters) برای کنترل کننده‌های ما (گوشی) و صفحه نمایش (رایانه) هستند. هر رویداد مخبری(emitted event) نیاز از که برای انتشار دوباره به تمامی سوکت‌ها از میان سرور گذر کند. در

‘server\index.js’
تعدادی مباشر(listener) در تابع ‘function’ اضافه کنید و سرور Node را از نو به کار اندازید.
socket.on(‘draw’, function(data) {
io.sockets.emit(‘draw’, data);
});
socket.on(‘erase’, function(data) {
io.sockets.emit(‘erase’, data);
});
socket.on(‘adjustFrame’, function(data) {
screenWidth = data.screenWidth;
screenHeight = data.screenHeight;
io.sockets.emit(‘adjustFrame’, data);
});

۱۷- چرخش گوشی
به localhost رایانه خود سر بزنید، این کار را از طریق گوشی خود از راه دور انجام دهید(همانند آنچه که با تبلت خود قبلا انجام دادید(. اکنون شما بایستی شاهد ترسیم خطی بر روی گوشی خود هنگامی که دکمه را می‌چرخانید باشید. در هر حال متوجه می‌شوید که دکمه‌ها اگر گوشی در حالت عمودی باشد به خوبی در صفحه جای نمی گیرند. این امر با CSS مرتفع می‌گردد:

@media screen and (orientation: portrait) {
.dial.big#dialVertical, .shadow.big#dialShadowVertical {
right: calc(50% – 75px);
bottom: 20px;
top: auto;
}
.dial.big#dialHorizontal, .shadow.big#dialShadowHorizontal {
left: calc(50% – 75px);
top: 20px;
}}

۱۸- کاردستی خود را واقعی تر کنید
اجازه برگردیم به نسخه تبلتی. متاسفانه API لرزاننده در iOS در دسترس نیست، بنابراین ما نمی توانیم بازخورد لمسی را زمانی که دکمه‌ها چرخانیده می‌شوند را پیاده سازی کنیم. در اسباب بازی اصلی، اما، شما می‌توانستید اثر انگشت هایی سیاه رنگی را با فشردن صفحه ایجاد کنید. ما می‌توانیم یک رویداد لمس(touch event) را برای دستگاه تعریف کنیم تا این ویژگی شبیه سازی شود. این مباشرین(listeners) را در init() تعریف کنید و توابعی را که آنها فرا می‌خوانند را کشف کنید:

If (type === ‘all’) {
canvas.addEventListener(‘touchstart’, function(e) {
e.preventDefault();
drawFingerPrint(e.layerX, e.layerY, true);
});
canvas.addEventListener(‘touchend’, function(e) {
hideFingerPrint(e.layerX, e.layerY);
});
}

۱۹- نگاهی دقیق تر به اثر انگشت ها
در متود drawFingerPrint()، قبل از آنکه هر کار دیگری را انجام دهیم، شرایط کنونی بوم(canvas) را در یک المان کپی می‌کنیم که از آن برای بازیابی رسم خود استفاده می‌کنیم زمانی که پرینت را خالی می‌کنیم. این فقط در اولین لمس اتفاق می‌افتد، و از آن در فراخوانی‌های بعدی که اندازه پرینت را در ۱۰۰ms افزایش می‌دهند خبری نیست.

Function drawFingerPrint(xPos, yPos, saveCanvas) {
/*partial function, refer to extra.js*/
If(saveCanvas) {
hiddenCanvas = document.
createElement(‘canvas’);
var hiddenContext = canvas.width;
getContext(‘۲d’);
hiddenCanvas.width = canvas.width;
hiddenCanvas.height = canvas.height;
hiddenContext.drawImge(canvas, 0, 0);
}
}

۲۰- اجرای آفلاین برنامه
اکنون شما می‌توانید برنامه خود را کاملا مستقل سازید، با ذخیره کردن آن بر روی تبلت خود به عنوان یک برنامه بر روی صفحه اصلی. ما نمی توانیم مشابه این کار را بر روی تلفن‌های همراه انجام دهیم، زیرا این کار اتصال به سرور را می‌طلبد. در ‘/public’، فایلی را به نام sketch.appcache پیدا کنید و همه ی ‘localhost’‌ها را با آدرس IP خودتان جایگزین کنید. حالا HTML را بدین شکل در آورید:

<html lang=”en” manifest=”sketch.appcache”>

۲۱- ذخیره کردن برنامه
دوباره به برنامه خود بر روی تبلت مراجعه کنید و گزینه ی اضافه بر صفحه اصلی را انتخاب فرمایید. یک نقشک(icon) جدید بر روی صفحه اصلی (desktop) شما نمایان خواهد شد. آن را تنها یک بار زمانی که هنوز از راه دور به localhost شما متصل است باز کنید. Cache manifest که ما از قبل ساخته بودیم تمامی فایل‌های لازم را در پس زمینه برای استفاده در هنگام آفلاین بودن دانلود می‌کند. وای فای را خاموش کرده، و دوباره برنامه را باز کنید. حض کنید!

 

فراتر از این
شما اکنون صاحب یک Etch A Sketch دیجیتال کامل هستید. برای بسط دادن بیشتر آن، شما می‌توانید صدای پاک کردن را در هنگام تکان دادن صفحه پیاده سازی کنید. یا به وسیله نگارش WebSocket، شما می‌توانید برنامه خود را به صفحه ای چند کاربره تبدیل کنید که مجهز به چندین رنگ برای خطوط می‌باشد. این گزینه می‌تواند حتی تبدیل به نوعی بازی گردد، که هدف در آن پوشاندن فضایی بیشتر نسبت به حریف و بستن قسمتی از صفحه با خط متعلق به خودتان باشد. افرادی که ذهن خلاق تری دارند، می‌توانند پس زمینه هایی متشکل از تصاویر را جایگزین کنند، و یا تشخیص لبه را افزوده کنند تا آنها را بدون زحمت ترسیم کنند.
اگر شما قصد دارید که بیشتر تمرین کرده باشید، می‌توانید وضعیت راهنمایی را پیاده سازی کنید که در آن چند بخش از خط قبل از کپی کردن رسم شوند. گزینه‌های بسیاری روی میز است.

نظر بدهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

It is main inner container footer text