จับภาพ web screenshot ด้วย Google PageSpeed API

บทความใหม่ ปีนี้ โดย Ninenik Narkdee
await pagespeed async web screenshot

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ await pagespeed async web screenshot



เนื้อหานี้จะมาให้แนวทางอย่างง่ายสำหรับการจับภาพหรือ
ดึงรูปภาพ web screenshot ทั้งแบบ desktop และ mobile
โดยเรียกใช้จาก Google PageSpeed ซึ่งเป็น API ที่ใช้สำหรับ
วิเคราะห์เนื้อหาของเว็บเพจ ตรวจเช็คประสิทธิภาพการแสดง และ
การทำงานของเว็บ เพื่อใช้เป็นแนวทางให้การปรับปรุงต่อไป
    ซึ่งเนื้อหานี้ จะไม่ได้พูดถึงการใช้งาน PageSpeed API โดยตรง
เป็นเพียงการใช้ความสามารถหรือวิธีการเรียกใช้งานของ API สำหรับดึง
ข้อมูลรูปภาพ web screenshot มาใช้เท่านั้น
    และเป้าหมายสำคัญ คือจะมาสอนการใช้งาน async และ await ใน 
javascript เพื่อทำความเข้าใจและเพิ่มความสามารถสำหรับไปประยุกต์ใช้ต่อได้
ซึ่งส่วนนี้จะอยู่ตอนท้ายของเนื้อหา ถ้าใครเคยได้อ่าน บทความเกี่ยวกับการใช่งาน 
Asynchronous Programming ในภาษา Dart ก็จะพอเข้าใจการทำงานเบื้องต้นได้
สามารถลองเข้าไปอ่่านเป็นแนวทางได้ที่
    การใช้งาน Asynchronous Programming ในภาษา Dart เบื้องต้น http://niik.in/949 
 
 
 

การจับภาพ Web screenshot ด้วย PageSpeed Api

    โดยส่วนนี้จะพูดถึงการใช่งาน Server Side Script หรือ PHP ในการจัดการ และส่วนที่สอง จะพูดถึง
การใช้งาน Client-side Script หรือ JavaScript 
    การใช้งาน PHP จะมีแนวทางให้ด้วยกัน 3 แนวทางคือ 
 
  1. การใช้งาน cURL 
  2. การใช้งาน file_get_contents 
  3. การใช้งาน Google PageSpeed Library 
 
    ทั้ง 3 วิธี จะเป็นเรียกใช้งานหรือทำการ request ไปยัง endpoint ของ api (url สำหรับดึงข้อมูล)
เกี่ยวกับการวัดประสิทธิภาพของเว็บใซต์ และเมื่อได้ข้อมูลกลับ ก็เลือกเฉพาะในส่วนของ screenshot
มาใช้งาน ข้อมูล screenshot เป็นข้อมูลรูปภาพหรือที่เรียกว่า ImageData URI ที่เราสามารถนำไปใช้งาน
เพิ่มเติมได้ ไม่ว่าจะบันทึกลงฐานข้อมูล หรือสร้างเป็นไฟล์ไว้ใช้งาน เป็นต้น
    ในที่นี้จะใช้วิธี ดึงข้อมูล screenshot มาแล้วแสดงผลลัพธ์รูป screenshot นั้นๆ
 

    การใช้งาน cURL 

    สามารถเรียกใช้งานได้ดังนี้
 
<?php
// เร่มต้นการทดสอบคำนวณเวลาการทำงาน calculate time
$time_start = microtime(true); 
// โค้ดไฟล์ dbconnect.php ดูได้ที่ http://niik.in/que_2398_5642
// require_once("../dbconnect.php"); // กำหนด path ให้ถูกต้อง

// กำหนด url เว็บไซต์ที่ต้องการ
$webURL= "https://www.ninenik.com";
$param = array(
	"strategy"=>"mobile", // comment ปิดส่วนนี้ หากต้องการผลแบบ desktop
	"url"=>$webURL
);
$qury_str = http_build_query($param);

// ใช้วิธี cURL
try{
	$endpontURL = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?".$qury_str;
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_URL, $endpontURL);
	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1);
	$result = curl_exec( $ch );
	$screen_shot_result = json_decode($result,true);
	$screen_shot = $screen_shot_result['lighthouseResult']['audits']['final-screenshot']['details']['data'];
}finally{
	curl_close( $ch );
}

if(isset($screen_shot) && !empty($screen_shot)){
	echo "<img src='{$screen_shot}' />";
}

// ส่วนกำหนดเวลาจบการทำงาน สำหรับคำนวณ
$time_end = microtime(true);
// เวลาการทำงาน ในหน่วยวินาที และ นาที
$execution_time_second = $time_end - $time_start;
$execution_time_minute = floor((int)$execution_time_second/60) + ((int)$execution_time_second%60)*0.01;

// เวลาในการทำงานปรมวลผลของคำสั่ง
echo '<br/><br/><b>Total Execution Time:</b> '.$execution_time_second.' Seconds';
echo '<br/><b>Total Execution Time:</b> '.$execution_time_minute.' Minutes';
?>
 

    การใช้งาน file_get_contents 

    สามารถเรียกใช้งานได้ดังนี้
 
<?php
// เร่มต้นการทดสอบคำนวณเวลาการทำงาน calculate time
$time_start = microtime(true); 
// โค้ดไฟล์ dbconnect.php ดูได้ที่ http://niik.in/que_2398_5642
// require_once("../dbconnect.php"); // กำหนด path ให้ถูกต้อง

// กำหนด url เว็บไซต์ที่ต้องการ
$webURL= "https://www.ninenik.com";
$param = array(
	"strategy"=>"mobile", // comment ปิดส่วนนี้ หากต้องการผลแบบ desktop
	"url"=>$webURL
);
$qury_str = http_build_query($param);

// ใช้ file_get_contents 
$screen_shot_json_data = file_get_contents("https://www.googleapis.com/pagespeedonline/v5/runPagespeed?".$qury_str);
$screen_shot_result = json_decode($screen_shot_json_data,true);
$screen_shot = $screen_shot_result['lighthouseResult']['audits']['final-screenshot']['details']['data'];

if(isset($screen_shot) && !empty($screen_shot)){
	echo "<img src='{$screen_shot}' />";
}

// ส่วนกำหนดเวลาจบการทำงาน สำหรับคำนวณ
$time_end = microtime(true);
// เวลาการทำงาน ในหน่วยวินาที และ นาที
$execution_time_second = $time_end - $time_start;
$execution_time_minute = floor((int)$execution_time_second/60) + ((int)$execution_time_second%60)*0.01;

// เวลาในการทำงานปรมวลผลของคำสั่ง
echo '<br/><br/><b>Total Execution Time:</b> '.$execution_time_second.' Seconds';
echo '<br/><b>Total Execution Time:</b> '.$execution_time_minute.' Minutes';
?>
 
 

    การใช้งาน Google PageSpeed Library 

    สำหรับการจับ web screenshot ไม่จำเป็นต้องใช้วิธีนี้ก็ได้ เพราะมีขั้นตอนที่ซับซ้อน ต้องดาวน์โหลด package มาใช้งาน
โดยสามารถติดตั้งผ่าน composer ดังนี้
 
composer require google/apiclient:"^2.0"
    ตัว Google APIs Client Library จะทำการดาวน์โหลด package ของ google service ต่างๆ มาให้เราใช้งาน รองรับการทำงาน
หลายอย่าง แต่ในที่นี้จะไม่ลงรายละเอียด จะฝากเป็นแค่โค้ดแนวทางไว้เท่านั้น ซึ่งทำการเรียกใช้งานผ่าน service account
 
    สามารถเรียกใช้งานได้ดังนี้
 
<?php
// เร่มต้นการทดสอบคำนวณเวลาการทำงาน calculate time
$time_start = microtime(true); 
// โค้ดไฟล์ dbconnect.php ดูได้ที่ http://niik.in/que_2398_5642
// require_once("../dbconnect.php"); // กำหนด path ให้ถูกต้อง


// include composer autoload
require_once '../vendor/autoload.php'; // กำหนด path ให้ถูกต้อง

// กำหนด server environment.กำหนดไฟล์ json ของ service account ที่สร้างใน google cloud console
putenv('GOOGLE_APPLICATION_CREDENTIALS=xxxxx.json');
// กำหนดการใช้งาน client api
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope("openid");
//var_dump($client);

// สร้าง HTTP Client
$httpClient = $client->authorize();

// สร้าง pagespeed service 
$pagespeedonlineService = new Google_Service_Pagespeedonline($client);
$pagespeedapi = $pagespeedonlineService->pagespeedapi;

// กำหนด url เว็บไซต์ที่ต้องการ
$webURL= "https://www.ninenik.com";
$param = array(
	"strategy"=>"mobile", // comment ปิดส่วนนี้ หากต้องการผลแบบ desktop
	"url"=>$webURL
);

// ใช้ google pagespeed library
$response = $pagespeedapi->runpagespeed($webURL,$param);
$screen_shot_result = $response;
$screen_shot = $screen_shot_result['lighthouseResult']['audits']['final-screenshot']['details']['data'];

if(isset($screen_shot) && !empty($screen_shot)){
	echo "<img src='{$screen_shot}' />";
}

// ส่วนกำหนดเวลาจบการทำงาน สำหรับคำนวณ
$time_end = microtime(true);
// เวลาการทำงาน ในหน่วยวินาที และ นาที
$execution_time_second = $time_end - $time_start;
$execution_time_minute = floor((int)$execution_time_second/60) + ((int)$execution_time_second%60)*0.01;

// เวลาในการทำงานปรมวลผลของคำสั่ง
echo '<br/><br/><b>Total Execution Time:</b> '.$execution_time_second.' Seconds';
echo '<br/><b>Total Execution Time:</b> '.$execution_time_minute.' Minutes';
?>
 
    จากการทดสอบการเรียกใช้งานทั้ง 3 วิธีข้างต้น ในการ request แต่ละครั้งจะใช้เวลาแตกต่างๆ กัน อยู่ที่ราวช่วงเวลา
การทำงานประมาณ 15 - 30 วินาที หากเกิน maximum execution time หรือทั่วไปใน PHP จะเท่ากับ 30 วินาที ก็อาจจะเกิด
error ได้  ขึ้นอยู่กับหลายปัจจัย เช่นการทำงานของ server ของเว็บ ที่เราเรียกใช้งานขณะนั้น หรือแม้แต่บางเว็บไซต์ก็ป้องกัน
การเรียกใช้งาน ก็อาจจะไม่สามารถเรียกดูข้อมูลได้ อีกอย่าง การเรียก api นี้ ไม่ใช่เป็นการเรียกข้อมูลของ screenshot โดยตรง
แต่เป็นการดูข้อมูลเกี่ยวกับประสิทธิภาพหรือความเร็วของเว็บไซต์ที่เราต้องการ จึงมีข้อมูลจำนวนมาก ถูกส่งกลับมา ซึ่งเราก็อาศัย
ข้อมูล screenshot ในนั้นมาใช้นั่นเอง
 
    ดูผลลัพธ์รูป screenshot ทั้งแบบ desktop และ mobile ด้านล่าง
 
 



 
 
    จะเห็นว่าเวลาของ request ทั้งสองไม่เท่ากัน เป็นรูปการแสดงผลของเว็บไซต์แบบ desktop และแบบ mobile หรือหน้าจอขนาด
เล็ก ดังนั้นรูปผลลัพธ์ที่ได้ ก็จะขึ้นกับว่าเว็บไซต์เรา กำหนดให้รองรับการใช้งานแบบ responsive หรือไม่ ด้วย
 
    แนวทางการใช้งาน PageSpeed API สามารถดูเพิ่มเติ้มได้ที่ PageSpeed Insights API
    
 
 
    ต่อไปเราจะดูในส่วนที่สองกันต่อ เป็นการทำงานฝั่งของ Client side script โดยใช้งาน JavaScript เรียกใช้งาน PageSpeed 
Insights API ถ้าเราเข้าไปตามลิ้งค์ PageSpeed API ด้านบน ก็จะมีตัวอย่างการใช้งานในฝั่ง Client สามารถนำโค้ด javascript 
ในตัวอย่าง ไปทดสอบได้ แต่จะเป็นข้อมูลการเรียกใช้งานเกี่ยวกับการวิเคราะห์เว็บไซต์ด้วย PageSpeed API
    เปลี่ยน url เป็นเว็บไซต์ที่เราค้องการทดสอบ จะได้ผลลัพธ์ประมาณนี้
 
 

 
 
    เรามาดูในส่วนของ script คำสั่ง
 
<script>
function run() {
  const url = setUpQuery();
  fetch(url)
    .then(response => response.json())
    .then(json => {
      // See https://developers.google.com/speed/docs/insights/v5/reference/pagespeedapi/runpagespeed#response
      // to learn more about each of the properties in the response object.
      showInitialContent(json.id);
      const cruxMetrics = {
        "First Contentful Paint": json.loadingExperience.metrics.FIRST_CONTENTFUL_PAINT_MS.category,
        "First Input Delay": json.loadingExperience.metrics.FIRST_INPUT_DELAY_MS.category
      };
      showCruxContent(cruxMetrics);
      const lighthouse = json.lighthouseResult;
      const lighthouseMetrics = {
        'First Contentful Paint': lighthouse.audits['first-contentful-paint'].displayValue,
        'Speed Index': lighthouse.audits['speed-index'].displayValue,
        'Time To Interactive': lighthouse.audits['interactive'].displayValue,
        'First Meaningful Paint': lighthouse.audits['first-meaningful-paint'].displayValue,
        'First CPU Idle': lighthouse.audits['first-cpu-idle'].displayValue,
        'Estimated Input Latency': lighthouse.audits['estimated-input-latency'].displayValue
      };
      showLighthouseContent(lighthouseMetrics);
    });
}

function setUpQuery() {
  const api = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
  const parameters = {
    url: encodeURIComponent('https://www.ninenik.com')
  };
  let query = `${api}?`;
  for (key in parameters) {
    query += `${key}=${parameters[key]}`;
  }
  return query;
}

function showInitialContent(id) {
  document.body.innerHTML = '';
  const title = document.createElement('h1');
  title.textContent = 'PageSpeed Insights API Demo';
  document.body.appendChild(title);
  const page = document.createElement('p');
  page.textContent = `Page tested: ${id}`;
  document.body.appendChild(page);
}

function showCruxContent(cruxMetrics) {
  const cruxHeader = document.createElement('h2');
  cruxHeader.textContent = "Chrome User Experience Report Results";
  document.body.appendChild(cruxHeader);
  for (key in cruxMetrics) {
    const p = document.createElement('p');
    p.textContent = `${key}: ${cruxMetrics[key]}`;
    document.body.appendChild(p);
  }
}

function showLighthouseContent(lighthouseMetrics) {
  const lighthouseHeader = document.createElement('h2');
  lighthouseHeader.textContent = "Lighthouse Results";
  document.body.appendChild(lighthouseHeader);
  for (key in lighthouseMetrics) {
    const p = document.createElement('p');
    p.textContent = `${key}: ${lighthouseMetrics[key]}`;
    document.body.appendChild(p);
  }
}

run();
</script>
 
    จะเห็นว่ารูปแบบการทำงานก็จะคล้ายๆ กับฝั่ง Server โดยในตัวอย่างมีการใช้งาน Fetch API 
 เราสามารถใช้ Ajax ของ jQuery ได้ถ้าต้องการ 
    Fetch API จะทำการเรียกไปยัง endpoint url โดยส่งค่า parameter ตามรูปแบบที่ api กำหนด
เมื่อได้ข้อมูลมาก็ทำการเรียกใช้งานผ่านตัวแปร object ซึ่งข้อมูลจาก api ที่ได้กลับก็จะเป็น json object
    สังเกตุรูปแบบการใช้งาน Fetch API จะอยู่ในลักษณะ ดังนี้
 
fetch(url)
.then(response => response.json())
.then(data => {
	// ข้อมูลสุดท้าย
});
 
    ให้จำไว้เสมอว่า เมื่อใดก็ตาม ที่มีเรื่องของเวลาที่ต้องรอ เข้ามาเกี่ยวข้อง รูปแบบการทำงานนั้น จะเป็นแบบ
asynchronous การทำงานของ Fetch API ก็เหมือนกัน เมื่อเราทำการ request ไป เราต้องต้องรอค่าที่จะถูกส่งกลับมา
ซึ่งค่านั้นจะมาในอนาคตข้างหน้า หรือในลำดับที่จะตามมาภายหลัง การใช้งาน Fetch API นี้จึงเกิด Promise Object ขึ้น
เป็น object ที่แสดงถึงความสมบุรณ์ของข้อมูลผลลัพธ์สุดท้าย จะเกิดขื้นภายหลังตามลำดับ อาจจะคืนค่าสำเร็จ หรือาจจะ
ล้มเหลวก็ได้ ดังนั้น เราจะเห็นคำว่า then() เชื่อมกับการทำงานของคำสั่ง นั่นคือ ถ้าข้อมูลส่งกลับมาแล้วให้ทำสั่งใดๆ ต่อ
ในตัวอย่างข้างต้น จะมี then() สองครั้ง ครั้งแรก  response ที่ได้ จะเป็น Body ของ response (ข้อมุลจาก การ request
จะมีส่วนของ header และ body หากอยากรู้เพิ่มเติมลองดูเกี่ยวกับ Nodejs ที่ลิ้งค์นี้ http://niik.in/905
    คำสั่ง 
 
response => response.json()
    เป็นรูปแบบ arrow ฟังก์ชั่น คือ response เป็นข้อมูล Response stream ที่ส่งกลับมา หรือก็คือส่วนของ Body เราทำการ
ส่งค่า Body ในชื่อตัวแปรว่า response ส่งเข้ามา และแปลงเป็น JSON Object ด้วยการเรียกใช่งาน คำสั่ง json() ดังนั้น
response.json() ก็คือ Body.json() นั่นเอง และฟังก์ชั่นนี้ก็มีเวลาที่ต้องรอเข้ามาเกี่ยวของ เพราะต้องใช้เวลาในการแปลงข้อมูล
จึงคืนค่าเป็น Promise Object อีก และจึงมีการเรียกใช้ then() ตัวที่สอง คือนำค่า data ซึ่งก็คือ reponse.json() หรือ ข้อมูล
JSON Object ที่แปลงค่าแล้วไปใช้งานต่อไปนั้นเอง
 
 

    การใช้งาน async และ await ร่วมกับ Fetch API

    ก่อนที่จะไปในรายละเอียดการใช้งาน async และ await เราจะมาดูรูปแบบคำสั่งการจับภาพ web screenshot โดยใช้ javascript
ในที่นี้ใช้งาน jquery แบบ slim ร่วมด้วย
 
<script  src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
      crossorigin="anonymous"></script>
<script>
// เรียกใช้งานฟังก์ชั่นสำหรับดึงข้อมูลรุป screenshot ใช้โหมดสำหรับมือถือ
runpagespeed("https://www.ninenik.com",true);
// ฟังก์ชั่น จับภาพ web screenshot
function runpagespeed(url, mobile){
	const api = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
	var parameters = {
		url: url
	};  
	if(mobile){
		parameters["strategy"] = "mobile";
	}
	let query = `${api}?`;
	query += $.param(parameters);
	let screenshotData;  
	fetch(query)
	.then(response => response.json())
	.then(json => {
		let lighthouse = json.lighthouseResult;
		screenshotData = lighthouse.audits['final-screenshot']['details'].data;
		console.log(screenshotData);  // ข้อมูลรุป อยู่ในรูปแบบ  data:image/jpeg;base64,/9j/4AAQSkZ.......
	});	
}
</script>
    คำสั่งข้างต้น ทดสอบการทำงาน และดูผลลัพธ์ผ่าน console เราเรียกใช้งานฟังก์ชั่น runpagespeed() โดยกำหนด url เว็บที่
เราต้องการข้อมูลรูป screenshot และกำหนด true สำหรับต้องการเป็นหน้าจอแบบ mobile หรือ เป็น false สำหรับหน้าจอ desktop
เมื่อเราทำการเรียกใช้งาน โปรแกรมก็จะทำงานสักครู่ประมาณ 15-30 วินาที ตามระยะเวลาการ request ที่ได้กล่าวไปแล้วด้านบน
รอสักพัก ข้อมูลของรูปก็จะแสดงในส่วนของ console ในรูปแบบ  data:image/jpeg;base64,/9j/4AAQSkZ.......
    โค้ดข้างต้น เราสามารถไปประยุกต์ หรือทำ UI ให้น่าใช้เพิ่มเติมได้ตามต้องการ เช่น มีช่องให้กรอก url ที่ต้องการจากนั้น เมื่อ
คลิกปุ่มสักปุ่มเช่น ปุ่มว่า "Get Screenhot" ก็อาจจะให้ขึ้นข้อความกำลังโหลด.. แสดงสถานะการทำงาน และเมื่อได้ค่าข้อมูลรูป
มาแล้วก็อาจจะนำมาแสดงโดยเพิ่มไปในค่า src ของ img แท็ก แบบนี้เป็นต้นได้ ตัวอย่าง
 

    ไฟล์ web_screenshot_1.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset='utf-8' />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-2/css/all.min.css">  
	<title>Document</title>
</head>
<body>

<div class="wrap-form w-50 mx-auto mt-5">
	<div class="input-group mb-3">
	<div class="input-group-prepend">
	  <select class="custom-select" name="view_mode">
		<option value="0">Desktop</option>
		<option value="1">Mobile</option>
	  </select>	  		
	  </div>  
	  <input type="url" class="form-control" name="web_url" value="https://www.ninenik.com" placeholder="กรอก url">	  
	  <div class="input-group-append">
		<button class="btn btn-primary" type="button" name="btn_screenshot">Get Screenshot</button>
	  </div>
	</div>
</div>
<div class="loading-widget w-50 mx-auto align-items-center d-none">
	<div class="spinner-border text-primary mr-2" role="status"></div>
	<strong class="text-primary">Loading...</strong>
</div>
<div class="wrap-show w-50 mx-auto">

</div>
<script  src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
      crossorigin="anonymous"></script>
<script>
$(function(){
	$('[name="btn_screenshot"]').on("click", function(){
		const btn = $(this); // ปุ่มกด
		const modeSelect = $('[name="view_mode"]').val(); // โหมดที่เลือก
		const webUrl = $('[name="web_url"]').val(); //url เว็บ
		btn.attr("disabled",true);
		var mode = (modeSelect==0)?false:true;
		$(".loading-widget.d-none").toggleClass("d-none d-flex"); // loading ที่ซ่อนให้แสดง
		$(".wrap-show").html(""); // รีเซ้ตข้อมูลรูปที่แสดงก่อนหน้า
		// เรียกใช้งานฟังก์ชั่นสำหรับดึงข้อมูลรุป screenshot 
		runpagespeed(webUrl, mode);
		
		// หากเลยเวลาที่กำหนดนี้ ไม่ว่าจะมีข้อมูลกลับมาหรือไม่ ให้ รีเซ็ต พร้อมรับคำสั่งใหม่
		setTimeout(function(){
			$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
			btn.removeAttr("disabled"); // ปุ่มกดได้อีกคั้ง
		},30000); // 30 วินาที
	});

})
// เรียกใช้งานฟังก์ชั่นสำหรับดึงข้อมูลรุป screenshot ใช้โหมดสำหรับมือถือ
//runpagespeed("https://www.ninenik.com",true);
// ฟังก์ชั่น จับภาพ web screenshot
function runpagespeed(url, mobile){
	const api = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
	var parameters = {
		url: url
	};  
	if(mobile){
		parameters["strategy"] = "mobile";
	}
	let query = `${api}?`;
	query += $.param(parameters);
	let screenshotData;  
	fetch(query)
	.then(response => response.json())
	.then(json => {
		let lighthouse = json.lighthouseResult;
		screenshotData = lighthouse.audits['final-screenshot']['details'].data;
		let img = $("<img>",{
			src:screenshotData,
			class:"img-fluid"
		});
		$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
		$('[name="btn_screenshot"]').removeAttr("disabled");// ปุ่มกดได้อีกคั้ง
		$(".wrap-show").append(img); // แสดงรูป screenshot
	});	
}
</script>

</body>
</html>
 
    ดูตัวอย่างการทำงานได้ที่ DEMO 1 ด้านล่าง
 
    ตอนนี้เราสามารถดึงข้อมูล web screenshot มาแสดงได้แล้ว  แล้วจำเป็นไหม ที่เราต้องรู้จักการใช้งาน async กับ await 
ในเมื่อ Fetch API ข้างต้นก็ทำงานได้ตามต้องการแล้ว
    หากใช้งานตามรูปแบบที่กล่าวไปแล้ว ก็ไม่จำเป็นที่เราต้องการใช้งาน async และ await แต่ในความเป็นจริง เราอาจจะต้องการ
รูปแบบหรือการปรับแต่งเพิ่มเติม เช่น เราไม่ต้องการที่จะทำงานคำสั่งแสดงข้อมูลในฟังก์ชั่น runpagespeed() เราต้องการคืนค่าข้อมูล
ออกมาเพื่อใช้งานต่อ โดยใช้รูปแบบคร่าวๆ ประมาณนี้
 
function runpagespeed(url, mobile){
/// โค้ดตัดมาบางส่วน
	let screenshotData;  
	fetch(query)
	.then(response => response.json())
	.then(json => {
		let lighthouse = json.lighthouseResult;
		screenshotData = lighthouse.audits['final-screenshot']['details'].data;
		return screenshotData;
	});	
}
    และเรียกใช้งานเป็นดังนี้
 
let screenshotData = runpagespeed(webUrl, mode);
console.log(screenshotData); // จะเป็น undefined
if(screenshotData){

}
 
    การเรียกใช้งานคำสั่ง runpagespeed() เราจะได้ค่าข้อมูล screenshotData เป็น undefined ทั้งนี้เพราะ เมื่อเราเรียกใช้งาน
ตัวฟังก์ชั่น จะคืนค่าออกมาทันที ทั้งที่ตัว fetch ยังทำงานไม่เสร็จ ข้อมูลยังไม่กลับมาหรือลำดับข้อมูลสุดท้ายที่จะเกิดขึ้น ยัง
ไม่มาถึง ค่าที่คืนออกมาจากฟังก์ชั่นจึงเป็น undefined 
    จึงเป็นเหตุผลที่ว่า ถ้าต้องการใช้งานในลักษณะนี้ ฟังก์ชั่น runpagespeed() ก็ต้องเป็นฟังก์ชั่นที่ต้องรอเวลา นั่นคือเราต้องบอก
หรือกำหนดว่าฟังก์ชั่นนี้ ต้องรอข้อมูลสุดท้ายก่อน แล้วค่อยคืนค่ากลับออกมา โดยการใช้ keyword ที่ชื่อ async ไว้ด้านหน้าฟังก์ชั่น
ตัวอย่างเช่น
 
function myfunc(){ }
    ก็กำหนดเป็น 
 
async function myfunc(){ }
    นั่นคือ async ใช้กำหนดให้กับฟังก์ชั่นที่ต้องการ หรือมีเรื่องของเวลาที่ต้องรอเข้ามาเกี่ยวกับข้อง และเราต้องการรอให้ฟังก์ชั่นนั้น
ทำงานจนกว่าข้อมูลสุดท้ายจะถูกส่งออกมา
 
    ส่วน await เราก็จะใช้ในตอนเรียกใช้งานฟังก์ชั่นที่มีเรื่องของเวลาที่ต้องรอเข้ามาเกี่ยวข้อง นั่นคือ
 
let returnvalue = await myfunc(); // รอจนกว่าค่าสุดท้ายที่ได้จากฟังก์ชัน myfunc() จะถูกส่งมาแล้วถึงทำงานบรรทัดต่อไป
    โดยการเรียกใช้งาน await เราจะเรียกใช้งานได้เฉพาะฟังก์ชั่นที่เป็น async เท่านั้น นั่นคือเราจะต้องเรียกใช้งาน await ในลักษณะ
ดังต่อไปนี้
 
async function myfunc_2() {
    let returnvalue = await myfunc();
}
    ถ้าใช้ร่วมกับปุ่มที่กำหนดการทำงานเมื่อคลิกด้วย jquery ก็จะกำหนดประมาณนี้
 
$('[name="btn_screenshot"]').on("click", async function(){
	let returnvalue = await myfunc();
});
    เมื่อเรารู้แล้วว่า await สามารถกำหนดให้กับ การเรียกใช้งานฟังก์ชั่นที่มีเรื่องเวลาที่ต้องรอเข้ามาเกี่ยวข้อง ดังนั้น เราก็สามารถ
เรียกใช้งานกับคำสั่ง fetch() และ response.json() ได้ เราก็จะได้ runpagespeed() เป็นดังนี้
 
async function runpagespeed(url, mobile){
/// โค้ดตัดมาบางส่วน
	let screenshotData;  
	let response = await fetch(query);
	let json = await response.json();
	let lighthouse = json.lighthouseResult;
	screenshotData = lighthouse.audits['final-screenshot']['details'].data;
	return screenshotData;
}
    เราก็จะได้ไฟล์ ที่มีการใช้งาน async และ await ร่วมกับ Fetch API ดังนี้
 

    ไฟล์ web_screenshot_2.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset='utf-8' />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-2/css/all.min.css">  
	<title>Document</title>
</head>
<body>

<div class="wrap-form w-50 mx-auto mt-5">
	<div class="input-group mb-3">
	<div class="input-group-prepend">
	  <select class="custom-select" name="view_mode">
		<option value="0">Desktop</option>
		<option value="1">Mobile</option>
	  </select>	  		
	  </div>  
	  <input type="url" class="form-control" name="web_url" value="https://www.pantip.com" placeholder="กรอก url">	  
	  <div class="input-group-append">
		<button class="btn btn-success" type="button" name="btn_screenshot">Get Screenshot</button>
	  </div>
	</div>
</div>
<div class="loading-widget w-50 mx-auto align-items-center d-none">
	<div class="spinner-border text-success mr-2" role="status"></div>
	<strong class="text-success">Loading...</strong>
</div>
<div class="wrap-show w-50 mx-auto">

</div>
<script  src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
      crossorigin="anonymous"></script>
<script>
$(function(){
	$('[name="btn_screenshot"]').on("click", async function(){
		const btn = $(this); // ปุ่มกด
		const modeSelect = $('[name="view_mode"]').val(); // โหมดที่เลือก
		const webUrl = $('[name="web_url"]').val(); //url เว็บ
		btn.attr("disabled",true);
		var mode = (modeSelect==0)?false:true;
		$(".loading-widget.d-none").toggleClass("d-none d-flex"); // loading ที่ซ่อนให้แสดง
		$(".wrap-show").html(""); // รีเซ้ตข้อมูลรูปที่แสดงก่อนหน้า
		// เรียกใช้งานฟังก์ชั่นสำหรับดึงข้อมูลรุป screenshot 
		let screenshotData = await runpagespeed(webUrl, mode); // รอข้อมูล
		if(screenshotData){ // มีข้อมูล นำมาแสดง
			let img = $("<img>",{
				src:screenshotData,
				class:"img-fluid"
			});
			$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
			$('[name="btn_screenshot"]').removeAttr("disabled");// ปุ่มกดได้อีกคั้ง
			$(".wrap-show").append(img); // แสดงรูป screenshot				
		}
		
		// หากเลยเวลาที่กำหนดนี้ ไม่ว่าจะมีข้อมูลกลับมาหรือไม่ ให้ รีเซ็ต พร้อมรับคำสั่งใหม่
		setTimeout(function(){
			$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
			btn.removeAttr("disabled"); // ปุ่มกดได้อีกคั้ง
		},30000); // 30 วินาที
	});

})
// เรียกใช้งานฟังก์ชั่นสำหรับดึงข้อมูลรุป screenshot ใช้โหมดสำหรับมือถือ
//runpagespeed("https://www.ninenik.com",true);
// ฟังก์ชั่น จับภาพ web screenshot
async function runpagespeed(url, mobile){
	const api = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
	var parameters = {
		url: url
	};  
	if(mobile){
		parameters["strategy"] = "mobile";
	}
	let query = `${api}?`;
	query += $.param(parameters);
	let screenshotData;  
	let response = await fetch(query);
	let json = await response.json();
	let lighthouse = json.lighthouseResult;
	screenshotData = lighthouse.audits['final-screenshot']['details'].data;
	return screenshotData;
}
</script>

</body>
</html>
 
    ดูตัวอย่างการทำงานใน DEMO 2 ผลลัพธ์ และหน้าตาคล้ายกับ DEMO 1 แต่ใช้รูปแบบการกำหนด async และ await 
ร่วมด้วย และสามารถนำไปประยุกต์เพิ่มเติมได้
 
    ส่วน DEMO 3 โค้ดประยุกต์เพิ่ม จะไม่ลงรายละเอียดการทำงาน หากทำความเข้าใจเนื้อหาข้างต้นที่ได้กล่าวไปแล้ว
สามารถลองพิจารณาการทำงานของโปรแกรมได้ 
    โดยตัวอย่างสุดท้ายนี้ เป็นการให้ผู้ใช้เลือกได้ว่า จะใช้แบบ Desktop หรือ Mobile หรือ ทั้งสอง (Both) นั่นหมายความ
ว่าหากเลือกเป็นทั้งสอง ก็จะมีการ request 2 ครั้ง เวลาการทำงานก็จะเพิ่มขึ้น อยู่่ในช่วง 30 - 60 วินาทีในการรอข้อมูลทั้งสอง
ทดสอบการทำงานที่ DEMO 3 ด้านล่าง
 
 

    ไฟล์ web_screenshot_3.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset='utf-8' />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-2/css/all.min.css">  
	<title>Document</title>
</head>
<body>

<div class="wrap-form w-50 mx-auto mt-5">
	<div class="input-group mb-3">
	<div class="input-group-prepend">
	  <select class="custom-select" name="view_mode">
		<option value="0">Desktop</option>
		<option value="1">Mobile</option>
		<option value="2">Both</option>
	  </select>	  		
	  </div>  
	  <input type="url" class="form-control" name="web_url" value="https://www.twitter.com" placeholder="กรอก url">	  
	  <div class="input-group-append">
		<button class="btn btn-danger" type="button" name="btn_screenshot">Get Screenshot</button>
	  </div>
	</div>
</div>
<div class="loading-widget w-50 mx-auto align-items-center d-none">
	<div class="spinner-border text-danger mr-2" role="status"></div>
	<strong class="text-danger">Loading...</strong>
</div>
<div class="wrap-show w-50 mx-auto">

</div>
<script  src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
      crossorigin="anonymous"></script>
<script>
$(function(){
	$('[name="btn_screenshot"]').on("click", async function(){
		const btn = $(this); // ปุ่มกด
		const modeSelect = $('[name="view_mode"]').val(); // โหมดที่เลือก
		const webUrl = $('[name="web_url"]').val(); //url เว็บ
		btn.attr("disabled",true);
		const modeText = ["desktop","mobile","both"];
		var mode = modeText[modeSelect];
		$(".loading-widget.d-none").toggleClass("d-none d-flex"); // loading ที่ซ่อนให้แสดง
		$(".wrap-show").html(""); // รีเซ้ตข้อมูลรูปที่แสดงก่อนหน้า
		
		// รอข้อมูล array รูป screenshot ตามเงื่อนไขที่เลือก
		var webscreenshot = await getscreenshot(webUrl,mode);
		if(webscreenshot){
			webscreenshot.forEach((value) => { // วนลูปแสดงรูป
				var img = $("<img>",{
					src:value,
					class:"img-fluid"
				});
				$('.wrap-show').append(img);
			});		
			$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
			$('[name="btn_screenshot"]').removeAttr("disabled");// ปุ่มกดได้อีกคั้ง				
		}
		
		// หากเลยเวลาที่กำหนดนี้ ไม่ว่าจะมีข้อมูลกลับมาหรือไม่ ให้ รีเซ็ต พร้อมรับคำสั่งใหม่
		setTimeout(function(){
			$(".loading-widget.d-flex").toggleClass("d-none d-flex"); // loading ที่โชว์ให้ซ่อนไป
			btn.removeAttr("disabled"); // ปุ่มกดได้อีกคั้ง
		},60000); // 60 วินาที กรณีนี้ เผื่อไว้ 60 เพราะถ้าดึงสองแบบ จะใช้เวลามากกว่า 30 วินาที
	});

})
// สร้างฟังก์ชั่นเก็บข้อมูล screenshot ไว้ใน array ก่อนคืนค่ากลับไปใช้งาน
async function getscreenshot(url, mode){
	var screenshot = [];
	switch(mode){
		case 'desktop':
			var screenshot1 = await runpagespeed(url, false);
			screenshot.push(screenshot1);
			break;
		case 'mobile':
			var screenshot2 = await runpagespeed(url, true);			
			screenshot.push(screenshot2);
			break;
		case 'both':
			var screenshot1 = await runpagespeed(url, false);
			screenshot.push(screenshot1);
			var screenshot2 = await runpagespeed(url, true);	
			screenshot.push(screenshot2);
			break;			
		default:
			var screenshot1 = await runpagespeed(url, false);
			screenshot.push(screenshot2);
			break;	
	}
	return screenshot;
}
// ฟังก์ชั่น จับภาพ web screenshot
async function runpagespeed(url, mobile){
	const api = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
	var parameters = {
		url: url
	};  
	if(mobile){
		parameters["strategy"] = "mobile";
	}
	let query = `${api}?`;
	query += $.param(parameters);
	let screenshotData;  
	let response = await fetch(query);
	let json = await response.json();
	let lighthouse = json.lighthouseResult;
	screenshotData = lighthouse.audits['final-screenshot']['details'].data;
	return screenshotData;
}
</script>

</body>
</html>
 
    หวังว่าเนื้อหาข้างต้น จะเป็นประโยชน์ ในการทำความเข้าใจ และประยุกต์ใช้งานต่อไป แม้ว่า web screenshot อาจจะไม่
เป้าหมายหลักของเนื้อหา แต่ส่วนของหลักการทำงานของโปรแกรมที่แนะนำ เป็นส่วนสำคัญ
 






กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ







เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ