• July 3, 2026

    แก้โหลด pdf จากมือถือนาน

    เมื่อกดปุ่มดาวน์โหลด จาก มือถือ หน้า pdf ส่วนของ ลายเซ็น จะหมุนๆ นานมากๆ ไม่เสร็จซะที แต่เมลส่งเข้า รวมทั้งลายเซ็น ทำให้เซฟ pdf ในมือถือตอนนั้นไม่ได้ (เดสทอปเซฟได้ปกติ)

    สาเหตุหลักที่บนมือถือเกิดอาการ “หมุนค้าง นานมากๆ ไม่เสร็จซะที” แต่เมลส่งผ่านปกติ และเดสท็อปเซฟได้ปกติ เกิดจากปัญหาสองจุดนี้ในโค้ดครับ:

    1. ฟังก์ชัน window.print(); บน Webview/เบราว์เซอร์มือถือ: ในฟังก์ชัน printContent พี่ใช้ระบบ window.print(); ซึ่งบนเดสท็อปจะเปิดหน้าต่าง Preview การพิมพ์ขึ้นมาให้เซฟเป็น PDF ได้ทันที แต่บนมือถือ (โดยเฉพาะถ้าเปิดผ่านแอปอย่าง Line, Messenger หรือเบราว์เซอร์บางตัว) คำสั่ง window.print() จะทำงานได้ไม่สมบูรณ์ หรือพยายามเรียกคำสั่งพิมพ์ของระบบปฏิบัติการขึ้นมาจนทำให้หน้าเว็บค้าง (หมุนวนไม่สิ้นสุด) เนื่องจากสถานะ isProcessing = true; ถูกล็อกค้างไว้และไม่มีวันปล่อย
    2. พี่โหลดไลบรารี่ html2pdf.js มาแล้วแต่ยังไม่ได้เรียกใช้: ใน <head> มีการดึง html2pdf.bundle.min.js มาเตรียมไว้แล้ว แต่ในฟังก์ชัน printContent กลับไปเรียก window.print(); แทน ซึ่งตัวไลบรารี่นี้สร้างมาเพื่อแก้ปัญหานี้บนมือถือโดยเฉพาะ (แปลง DOM เป็น PDF แล้วสั่งดาวน์โหลดตรงๆ ไม่ผ่านคำสั่ง Print ของเครื่อง)

    วิธีแก้ไข

    ให้พี่ปรับแก้โค้ดในฟังก์ชัน printContent(el) ด้านล่างนี้ โดยเปลี่ยนจากการใช้ window.print() มาใช้ html2pdf ในการเจนไฟล์ดาวน์โหลดแทนครับ

    โค้ดเดิมในไฟล์ของพี่ (บรรทัดประมาณ 480-485):

    JavaScript

      // ... โค้ดส่วนบนของ printContent ...
      
      var $printButton = $('#print, button, .signature-pad-wrapper, input[type="file"]');
      $printButton.css('display', 'none');
    
      window.print();
    
      // คืนค่าปุ่มและอินพุตอัปโหลดไฟล์ให้กลับมาโชว์บนหน้าเว็บปกติ
      $printButton.css('display', '');
      $('#' + el).find('#signature-image').css('display', 'none');
    }
    

    ให้เปลี่ยนเป็นโค้ดชุดนี้แทนครับ:

    JavaScript

      // ซ่อนปุ่มและอินพุตอัปโหลดไฟล์ไม่ให้ติดไปในเนื้อหาดาวน์โหลด PDF
      var $printButton = $('#print, button, .signature-pad-wrapper, input[type="file"]');
      $printButton.css('display', 'none');
    
      // ตั้งค่าสำหรับ html2pdf.js
      var opt = {
        margin:       [10, 10, 10, 10], // ระยะขอบ (บน, ซ้าย, ล่าง, ขวา) เป็นมิลลิเมตร
        filename:     'Application_' + applicantName.replace(/\s+/g, '_') + '.pdf',
        image:        { type: 'jpeg', quality: 0.98 },
        html2canvas:  { scale: 2, useCORS: true }, // scale: 2 เพิ่มความคมชัด, useCORS ดึงรูปข้ามโดเมน
        jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };
    
      // เรียกใช้ html2pdf แทน window.print()
      html2pdf().set(opt).from(document.getElementById(el)).save().then(function() {
          // คืนค่าปุ่มและสิ่งต่างๆ กลับมาโชว์บนหน้าเว็บปกติหลังจากเซฟ PDF เสร็จแล้ว
          $printButton.css('display', '');
          $('#' + el).find('#signature-image').css('display', 'none');
          isProcessing = false; // ปลดล็อกตัวแปรสถานะ
      }).catch(function(error) {
          console.error('pdf generation error:', error);
          $printButton.css('display', '');
          $('#' + el).find('#signature-image').css('display', 'none');
          isProcessing = false; 
      });
    }
    

    จุดที่โค้ดชุดใหม่นี้เข้าไปเคลียร์ปัญหา:

    • ทำงานจบในตัวเอง: html2pdf จะแปลงพื้นที่ใน #PDF ทั้งหมดรวมถึงลายเซ็นที่เป็น Base64 ออกมาเป็นไฟล์ PDF และยิงคำสั่งดาวน์โหลดลงเครื่องมือถือตรงๆ โดยไม่ไปสะดุดกับฟังก์ชันสั่งพิมพ์ของระบบมือถือ
    • ปลดล็อกอาการหมุนค้าง: การใช้โครงสร้าง .then() จะทำให้อินเทอร์เฟซปลดล็อกตัวเอง (isProcessing = false;) ทันทีที่การสร้าง PDF และส่งคำสั่งดาวน์โหลดสำเร็จ ทำให้หน้าจอไม่ค้างและปุ่มกดจะกลับมาทำงานตามปกติครับ

    ทดสอบแล้ว

    ในมือถือ เซฟเร็ว ไม่ดีดหน้าขึ้นมา เมื่อเปิดไฟล์ในมือถือ พบว่า เป็นหน้าว่าง

    การที่ดาวน์โหลดบนมือถือแล้วได้ “หน้าว่าง (Blank Page)” เกิดจากโครงสร้าง CSS และการจัดวางแท็ก <body> กับ <div> ในไฟล์ของพี่ไปขัดขวางการทำงานของ html2pdf.js (และ html2canvas) ครับ

    มีอยู่ 3 จุดหลัก ที่ทำให้มันมองเห็นหน้าฟอร์มเป็นหน้าว่างเมื่อเปิดในมือถือ:

    1. สไตล์ของ .main-contain มี position: absolute; และ overflow: hidden;: ตัวเก็บครอบทั้งหมดของพี่ถูกเซ็ตให้ล็อกหน้าจอและตัดส่วนที่เกินทิ้ง (overflow: hidden) ทำให้เมื่อ html2canvas พยายามจะวาดภาพหน้าเว็บในโหมดมือถือ มันจะมองไม่เห็นเนื้อหาที่อยู่ลึกลงไปจนกลายเป็นหน้าว่าง
    2. การวางโครงสร้างแท็ก HTML สลับตำแหน่ง: ในโค้ดของพี่มีแท็ก <div id="overlay"></div> และ <div class="main-contain"> โผล่มาคั่น ก่อนหน้าแท็ก <body> ซึ่งตามหลักมาตรฐาน (และระบบเรนเดอร์ PDF) แท็กควบคุมทั้งหมดต้องอยู่ภายใต้ <body> เท่านั้น ไม่งั้นมันจะหา Element #PDF ไม่เจอตอนประมวลผลบนมือถือ
    3. รูปภาพโลโก้หัวกระดาษ (head-bki.png): ถ้าลิ้งก์รูปภาพไม่มีคุณสมบัติ CORS ที่ถูกต้อง ตัวเจน PDF จะหยุดวาดส่วนที่เหลือทันทีบนมือถือ

    วิธีแก้ไขให้หายขาด (ได้ทั้งไฟล์เร็วและเนื้อหาครบ)

    ให้พี่ปรับปรุงโครงสร้าง HTML และฟังก์ชัน printContent ตามนี้ครับ:

    จุดที่ 1: ปรับโครงสร้างแท็กตรงช่วงกลางไฟล์ (สลับให้ body ครอบทุกอย่าง)

    ให้พี่ค้นหาบรรทัดช่วงจบของ </head> (ประมาณบรรทัดที่ 215-225) โค้ดเดิมของพี่จะเป็นแบบนี้:

    HTML

    </head>
    <div id="overlay"></div>	
    <div class="main-contain" style="background-color:transparent/*#e0e0e033;*/">
    <body>
    <div id="PDF">
    

    ให้สลับใหม่เป็นแบบนี้ครับ:

    HTML

    </head>
    <body>
    <div id="overlay"></div>	
    <div class="main-contain" style="background-color:transparent;">
    <div id="PDF">
    

    จุดที่ 2: เพิ่มการเคลียร์สไตล์ CSS ชั่วคราวก่อนเจน PDF

    ปรับฟังก์ชัน printContent(el) ในส่วนของ html2pdf ให้ส่ง Element เปล่าๆ ที่ไม่มีเอฟเฟกต์การย่อจอหรือ overflow: hidden เข้าไปประมวลผลแทน โดยแก้ไขฟังก์ชันให้เป็นแบบนี้ครับ:

    JavaScript

    function printContent(el){
      // [กันกดเบิ้ล] ถ้ากำลังทำงานอยู่ แล้วลูกค้ากดซ้ำเข้ามาอีกรอบ ให้เด้งเตือนและตัดจบเลย
      if (isProcessing) {
         alert("ระบบกำลังประมวลผลและส่งอีเมลอยู่ครับ กรุณารอสักครู่ ไม่ควรกดปุ่มซ้ำๆ ครับพี่");
         return false; 
      }
    
      // [กันสแปม Honeypot]
      var honeyPot = $('#honey_pot_field').val();
      if (honeyPot !== "") { console.log("Spam detected!"); return false; }
    
      // [ล็อกเรตดาวน์โหลด] ตรวจสอบว่ากดเกิน 3 ครั้งใน 10 นาทีหรือไม่
      if (!checkDownloadRateLimit()) {
         return false; 
      }
    
      // ปรับสถานะเป็น "กำลังประมวลผล" เพื่อล็อกปุ่มทันที
      isProcessing = true;
    
      var applicantName = document.getElementById('txt_name').value || 'ไม่ระบุชื่อ';
      var currentSigData = document.getElementById('signature-data').value;
    
      // ผูกค่าอินพุตลง DOM
      $('#' + el).find('input[type="text"], input[type="number"], textarea, select').each(function() {
        var $this = $(this);
        if ($this.is('select')) { $this.find('option:selected').attr('selected', 'selected'); } 
        else { $this.attr('value', $this.val()); }
      });
      $('#' + el).find('input[type="radio"]').each(function() {
        var $this = $(this);
        if ($this.is(':checked')) { $this.attr('checked', 'checked'); } 
        else { $this.removeAttr('checked'); }
      });
    
      var formData = new FormData();
      formData.append('is_cy_jar', honeyPot); 
      
      formData.append('applicant_name', applicantName);
      formData.append('dob', $('#txt_dob').val() || '-');
      formData.append('id_card', $('#txt_idcard').val() || '-');
      formData.append('address', $('#txt_address').val() || '-');
      formData.append('postcode', $('#txt_postcode').val() || '-');
      formData.append('tel', $('#txt_tel').val() || '-');
      formData.append('biz1', $('#txt_biz1').val() || '-');
      formData.append('biz2', $('#txt_biz2').val() || '-');
      formData.append('biz3', $('#txt_biz3').val() || '-');
      formData.append('biz4', $('#txt_biz4').val() || '-');
      formData.append('license_no', $('#txt_license').val() || '-');
      formData.append('issued_by', $('#txt_issued_by').val() || '-');
      formData.append('grad_school', $('#txt_grad_school').val() || '-');
      formData.append('grad_year', $('#txt_grad_year').val() || '-');
      formData.append('train1', $('#txt_train1').val() || '-');
      formData.append('train2', $('#txt_train2').val() || '-');
      formData.append('association', $('#txt_association').val() || '-');
      formData.append('expertise', $('input[name="expertise"]:checked').val() || 'ไม่ได้เลือก');
      formData.append('expertise_other', $('#txt_expertise_other').val() || '-');
      formData.append('own_hospital', $('input[name="own_hospital"]:checked').val() || 'ไม่ได้เลือก');
      formData.append('hospital_detail', $('#txt_hospital_detail').val() || '-');
      formData.append('patients_per_year', $('#txt_patients_per_year').val() || '-');
      formData.append('has_old_insurance', $('input[name="has_old_insurance"]:checked').val() || 'ไม่ได้เลือก');
      formData.append('old_ins_comp', $('#txt_old_ins_comp').val() || '-');
      formData.append('old_ins_period', $('#txt_old_ins_period').val() || '-');
      formData.append('old_ins_limit', $('#txt_old_ins_limit').val() || '-');
      formData.append('ins_action', $('input[name="ins_action"]:checked').val() || 'ไม่มี');
      formData.append('ins_special_term', $('input[name="ins_special_term"]:checked').val() ? 'ใช่' : 'ไม่ใช่');
      formData.append('has_special_term', $('input[name="has_special_term"]:checked').val() || 'ไม่ได้เลือก');
      formData.append('special_term_detail', $('#txt_special_term_detail').val() || '-');
      formData.append('has_claims', $('input[name="has_claims"]:checked').val() || 'ไม่ได้เลือก');
      formData.append('claims_detail', $('#txt_claims_detail').val() || '-');
      formData.append('select_plan', $('input[name="select_plan"]:checked').val() || 'ไม่ได้เลือกแผน');
      formData.append('signature', currentSigData);
      formData.append('shipping_address', $('#txt_shipping').val() || '-');
    
      var idcardFile = document.getElementById('file_idcard').files[0];
      if (idcardFile) { formData.append('file_idcard', idcardFile); }
      var licenseFile = document.getElementById('file_license').files[0];
      if (licenseFile) { formData.append('file_license', licenseFile); }
      var paymentFile = document.getElementById('file_payment').files[0];
      if (paymentFile) { formData.append('file_payment', paymentFile); }
    
      // ยิง AJAX ส่งหา PHP
      $.ajax({
        url: 'medsendmail.php',
        type: 'POST',
        data: formData,
        processData: false,
        contentType: false,
        success: function(response) { 
            console.log('ส่งอีเมลเรียบร้อย');
            recordDownloadSuccess();
        },
        error: function() { console.error('ระบบส่งเมลขัดข้อง'); },
        complete: function() {
            // คืนสถานะให้กดใหม่ได้
            isProcessing = false;
        }
      });
    
      // แสดงผลลายเซ็นในหน้าพิมพ์ดาวน์โหลด PDF
      if (currentSigData) {
        $('#' + el).find('#signature-image').attr('src', currentSigData).css('display', 'block');
      }
      
      // ซ่อนปุ่มและอินพุตอัปโหลดไฟล์ไม่ให้ติดไปในเนื้อหาดาวน์โหลด PDF
      var $printButton = $('#print, button, .signature-pad-wrapper, input[type="file"]');
      $printButton.css('display', 'none');
    
      // คลอน Element ออกมาเพื่อลบโครงสร้าง CSS ที่บล็อกหน้าจอออกเฉพาะตอนเจน PDF
      var element = document.getElementById(el);
      var workerElement = element.cloneNode(true);
      
      // ตั้งค่าออปชันสำหรับ html2pdf
      var opt = {
        margin:       [10, 10, 10, 10],
        filename:     'Application_' + applicantName.replace(/\s+/g, '_') + '.pdf',
        image:        { type: 'jpeg', quality: 0.98 },
        html2canvas:  { 
          scale: 2, 
          useCORS: true,
          scrollY: 0,
          scrollX: 0
        },
        jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };
    
      // รันคำสั่งแปลงไฟล์จาก Element ที่แยกสไตล์ออกมาแล้ว
      html2pdf().set(opt).from(workerElement).save().then(function() {
          // เมื่อเซฟเสร็จ คืนค่าทุกอย่างบนหน้าจอปกติ
          $printButton.css('display', '');
          $('#' + el).find('#signature-image').css('display', 'none');
      }).catch(function(error) {
          console.error('PDF Generation Error:', error);
          $printButton.css('display', '');
          $('#' + el).find('#signature-image').css('display', 'none');
      });
    }
    

    ส่วนปัญหาที่สองที่พี่เจอว่า “พอโหลดจากมือถือแล้วฟอร์มมันเรียงตัวแบบจอมือถือ (กลายเป็นแนวยาวลงมาด้านล่าง) ไม่สวยเหมือนหน้าจอเดสท็อป”

    สาเหตุเกิดจากตัวไลบรารี่ html2pdf.js มันทำงานด้วยหลักการจำลองหน้าจอฝั่งผู้ใช้งาน (Client-side Rendering) ครับ พอเปิดในมือถือ หน้าจอมันกว้างแค่ประมาณ 300px–400px ตัว CSS Media Query (@media only screen and (max-width: 700px)) ที่พี่เขียนคุมไว้ มันเลยทำงาน ทำให้ช่องกรอกมันดีดตัวขยายเต็ม 100% และตารางหดลงมาเพื่อให้อ่านง่ายบนจอโทรศัพท์ พอแคปออกมาเป็น PDF มันเลยได้หน้าตาเวอร์ชันมือถือติดมาด้วย

    วิธีแก้ไข

    เราต้องใช้วิธี “บังคับขนาดหน้าต่างจำลอง (Viewport)” ในตอนที่รันคำสั่งคลอน Element ออกมาทำ PDF ให้มันเห็นว่าตัวเองกำลังทำงานอยู่บนหน้าจอขนาดใหญ่ (เดสท็อป) ตลอดเวลาครับ ไม่ว่าจะกดสั่งจากเครื่องไหนก็ตาม

    ให้พี่ปรับแก้โค้ดในฟังก์ชัน printContent ตรงช่วงของการตั้งค่าออปชัน var opt = { ... } (อยู่ประมาณช่วงท้ายๆ ของฟังก์ชัน) โดยเพิ่มคำสั่งล็อกความกว้างเข้าไปดังนี้ครับ:

    โค้ดเดิมของพี่:

    JavaScript

      // ตั้งค่าออปชันสำหรับ html2pdf
      var opt = {
        margin:       [10, 10, 10, 10],
        filename:     'Application_' + applicantName.replace(/\s+/g, '_') + '.pdf',
        image:        { type: 'jpeg', quality: 0.98 },
        html2canvas:  { 
          scale: 2, 
          useCORS: true,
          scrollY: 0,
          scrollX: 0
        },
        jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };
    

    ให้พี่แก้เป็นชุดนี้แทนครับ:

    JavaScript

      // ตั้งค่าออปชันสำหรับ html2pdf
      var opt = {
        margin:       [10, 10, 10, 10],
        filename:     'Application_' + applicantName.replace(/\s+/g, '_') + '.pdf',
        image:        { type: 'jpeg', quality: 0.98 },
        html2canvas:  { 
          scale: 2, 
          useCORS: true,
          scrollY: 0,
          scrollX: 0,
          // บังคับให้ระบบเรนเดอร์ภาพที่ความกว้าง 950px เสมอ (ขนาดหน้าจอเดสท็อป) เพื่อไม่ให้สไตล์มือถือทำงาน
          windowWidth: 950 
        },
        jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };
    

    การทำงานของโค้ดนี้:

    การเพิ่ม windowWidth: 950 เข้าไปในส่วนของ html2canvas จะเป็นการหลอกสคริปต์ว่า “ถึงแม้ตอนนี้ลูกค้าจะกดปุ่มจากมือถือหน้าจอแคบๆ แต่ตอนที่เธอจะแปลงหน้าเว็บนี้เป็น PDF ให้เธอแสร้งทำเป็นว่าหน้าจอนี้กว้าง 950 พิกเซลนะ”

    ผลลัพธ์คือ สไตล์ของหน้าจอมือถือจะไม่ถูกเรียกใช้งาน และ PDF ที่ดาวน์โหลดออกมาจากโทรศัพท์จะมีหน้าตาตาราง ช่องกรอก และขนาดฟอนต์ จัดเรียงเรียบร้อยสวยงามเหมือนกับกดเซฟจากหน้าจอคอมพิวเตอร์แบบเป๊ะๆ 100% เลยครับพี่ ลองปรับแก้แล้วอัปโหลดไปทดสอบดูได้เลยครับ!

    สรุป ถามผิด
    โค๊ดต้นฉบับที่ส่งให้ ใช้ได้ดีกับทุก device แต่ที่เกิดปัญหากดปุ่มดาวน์โหลดแล้วไม่มี การดีด pdf เพราะ กดลิ้งผ่าน line
    ถ้าเข้าทาง broswer จะไม่มีปัญหานี้ ฉะนั้น ต้องแก้ ให้ใช้ได้ กับ การเข้าทางไลน์ด้วย …
    โค๊ดที่แก้นี้จึงไม่ได้ใช้ต่อไป ซึ่งมันเปลี่ยนจาก การแสดง preview เป็นแบบ เซฟเข้าเครื่องเลยด้วย



เวอไนน์ไอคอร์ส

ประหยัดเวลากว่า 100 เท่า!






เวอไนน์เว็บไซต์⚡️
สร้างเว็บไซต์ ดูแลเว็บไซต์

Categories


Uncategorized