Hello.

I am Paul Kinlan.

A Developer Advocate for Chrome and the Open Web at Google.

Puppeteer Go🔗

Paul Kinlan

Tôi yêu Puppeteer - nó cho phép tôi chơi xung quanh các ý tưởng của The Headless Web - đó là chạy web trong trình duyệt mà không có trình duyệt hiển thị và thậm chí xây dựng các công cụ như DOM-curl (Curl chạy JavaScript). Cụ thể tôi thích kịch bản trình duyệt để cạo, thao tác và tương tác với các trang.

Một bản demo tôi muốn thực hiện được lấy cảm hứng từ bài đăng Capturing 422 live images của Ire nơi cô ấy chạy một kịch bản múa rối sẽ điều hướng đến nhiều trang và chụp ảnh màn hình. Thay vì đi đến nhiều trang, tôi muốn chụp nhiều ảnh chụp màn hình các yếu tố trên trang.

Vấn đề mà tôi gặp phải với Puppeteer là khổ thơ mở đầu mà bạn cần phải làm bất cứ điều gì. Khởi chạy, tab Mở, điều hướng - nó không phức tạp, nó chỉ đơn giản hơn so với tôi muốn tạo cho các tập lệnh đơn giản. Đó là lý do tại sao tôi tạo ra Puppeteer Go . Đó chỉ là một tập lệnh nhỏ giúp tôi xây dựng các tiện ích CLI dễ dàng mở trình duyệt, điều hướng đến một trang, thực hiện hành động your và sau đó tự dọn sạch.

Kiểm tra nó ra.

const { go } = require('puppeteer-go');

go('https://paul.kinlan.me', async (page) => {
    const elements = await page.$$("h1");
    let count = 0;
    for(let element of elements) {
      try {
        await element.screenshot({ path: `${count++}.png`});
      } catch (err) {
        console.log(count, err);
      }
    }
});

Đoạn mã trên sẽ tìm thấy phần tử h1 trong blog của tôi và chụp ảnh màn hình. Đây không phải là nơi tốt như công việc của tôi, nhưng tôi nghĩ thật gọn gàng để xem liệu chúng ta có thể nhanh chóng kéo ảnh chụp màn hình từ canisuse.com trực tiếp từ trang hay không.

const { go } = require('puppeteer-go');

go('https://caniuse.com/#search=css', async (page) => {
    const elements = await page.$$("article.feature-block.feature-block--feature");
    let count = 0;
    for(let element of elements) {
      try {
        await element.screenshot({ path: `${count++}.png`});
      } catch (err) {
        console.log(count, err);
      }
    }
});
4.png
3.png
2.png
1.png
0.png

Thưởng thức!

About Me: Paul Kinlan

I lead the Chrome Developer Relations team at Google.

We want people to have the best experience possible on the web without having to install a native app or produce content in a walled garden.

Our team tries to make it easier for developers to build on the web by supporting every Chrome release, creating great content to support developers on web.dev, contributing to MDN, helping to improve browser compatibility, and some of the best developer tools like Lighthouse, Workbox, Squoosh to name just a few.

A simple video insertion tool for EditorJS🔗

Paul Kinlan

Tôi thực sự thích EditorJS . Nó cho phép tôi tạo một giao diện lưu trữ web rất đơn giản cho blog Hugo tĩnh của mình.

EditorJS có hầu hết những gì tôi cần trong một trình soạn thảo dựa trên khối đơn giản. Nó có một plugin cho các tiêu đề, mã và thậm chí là một cách đơn giản để thêm hình ảnh vào trình chỉnh sửa mà không yêu cầu cơ sở hạ tầng lưu trữ. Cho đến bây giờ, không có cách đơn giản nào để thêm video vào trình chỉnh sửa.

Tôi đã simple-image kho plugin và thay đổi nó lên (chỉ một chút) để tạo ra một simple-video plugin ( npm module ). Bây giờ tôi có thể bao gồm các video dễ dàng trong blog này.

Nếu bạn quen thuộc với EditorJS, việc đưa vào các dự án của bạn khá đơn giản. Chỉ cần cài đặt nó như sau

npm i simple-video-editorjs

Và sau đó chỉ cần đưa nó vào dự án của bạn khi bạn thấy phù hợp.

const SimpleVideo = require('simple-video-editorjs');

var editor = EditorJS({
  ...
  
  tools: {
    ...
    video: SimpleVideo,
  }
  
  ...
});

Trình chỉnh sửa có một số tùy chọn đơn giản cho phép bạn định cấu hình cách lưu trữ video trong trang:

  1. Tự động phát - video sẽ tự động phát khi tải trang
  2. tắt tiếng - theo mặc định, video sẽ không có âm thanh (cần thiết cho tự động phát)
  3. điều khiển - video sẽ có các điều khiển HTML mặc định.

Dưới đây là một ví dụ nhanh về video được nhúng (và hiển thị một số tùy chọn).

Dù sao, tôi đã rất vui khi tạo plugin nhỏ này - không quá khó để tạo và điều duy nhất tôi đã làm là trì hoãn việc chuyển đổi sang base64 mà hình ảnh đơn giản sử dụng và thay vào đó chỉ sử dụng URL Blob.

Test post Video upload

Paul Kinlan

Nếu bạn thấy một video ở đây, thì nó đã làm việc.

Read More

Friendly Project Name Generator with Zeit🔗

Paul Kinlan

Tôi đã có một số ý tưởng cho các dự án mà làm cho nó dễ dàng hơn để tạo ra các trang web trên web - một trong những ý tưởng là để thực hiện một netlify-like drag and drop interface cho zeit dự án dựa (Tôi như Zeit nhưng nó đòi hỏi một chút ma thuật cli để triển khai).

Bài đăng này chỉ bao gồm một phần nhỏ của câu đố: tạo tên dự án.

Glitch là một ví dụ tốt về điều này, khi bạn tạo một dự án, nó sẽ đặt cho nó một tên được tạo ngẫu nhiên hay thay đổi. Nhóm cũng tạo ra một good dictionary of fairly safe words kết hợp tốt (và nếu bạn muốn họ có một máy chủ đơn giản để lưu trữ).

Vì vậy, dự án phụ vào Chủ nhật tuần này là tạo ra một dịch vụ vi mô đơn giản để tạo tên dự án ngẫu nhiên bằng cách sử dụng serverless-functions của Zeit và từ điển từ Glitch.

And here it is ( code ), nó khá ngắn và không quá phức tạp.

const words = require("friendly-words");

function generate(count = 1, separator = "-") {
  const { predicates, objects } = words;
  const pCount = predicates.length;
  const oCount = objects.length;
  const output = [];

  for (let i = 0; i < count; i++) {
    const pair = [predicates[Math.floor(Math.random() * pCount)], objects[Math.floor(Math.random() * oCount)]];
    output.push(pair.join(separator));
  }

  return output;
}

module.exports = { generate }

Nếu bạn không muốn đưa nó trực tiếp vào dự án của mình, bạn có thể sử dụng điểm cuối HTTP để tạo tên dự án ngẫu nhiên (dưới dạng "XY") bằng cách gửi yêu cầu web tới https: // tên thân thiện với dự án. kinlan.now.sh/api/names, sẽ trả lại một cái gì đó như sau.

["momentous-professor"]

Bạn cũng có thể kiểm soát số lượng tên sẽ tạo với tham số chuỗi truy vấn là Count = x , ví dụ: https://friendly-project-name.kinlan.now.sh/api/names?count=100

["melon-tangerine","broad-jury","rebel-hardcover","far-friend","notch-hornet","principled-wildcat","level-pilot","steadfast-bovid","holistic-plant","expensive-ulna","sixth-gear","political-wrench","marred-spatula","aware-weaver","awake-pair","nosy-hub","absorbing-petunia","rhetorical-birth","paint-sprint","stripe-reward","fine-guardian","coconut-jumbo","spangle-eye","sudden-euphonium","familiar-fossa","third-seaplane","workable-cough","hot-light","diligent-ceratonykus","literate-cobalt","tranquil-sandalwood","alabaster-pest","sage-detail","mousy-diascia","burly-food","fern-pie","confusion-capybara","harsh-asterisk","simple-triangle","brindle-collard","destiny-poppy","power-globeflower","ruby-crush","absorbed-trollius","meadow-blackberry","fierce-zipper","coal-mailbox","sponge-language","snow-lawyer","adjoining-bramble","deserted-flower","able-tortoise","equatorial-bugle","neat-evergreen","pointy-quart","occipital-tax","balsam-fork","dear-fairy","polished-produce","darkened-gondola","sugar-pantry","broad-slouch","safe-cormorant","foregoing-ostrich","quasar-mailman","glittery-marble","abalone-titanosaurus","descriptive-arch","nickel-ostrich","historical-candy","mire-mistake","painted-eater","pineapple-sassafras","pastoral-thief","holy-waterlily","mewing-humor","bubbly-cave","pepper-situation","nosy-colony","sprout-aries","cyan-bestseller","humorous-plywood","heavy-beauty","spiral-riverbed","gifted-income","lead-kiwi","pointed-catshark","ninth-ocean","purple-toucan","tundra-cut","coal-geography","icy-lunaria","agate-wildcat","respected-garlic","polar-almandine","periodic-narcissus","carbonated-waiter","lavish-breadfruit","confirmed-brand","repeated-period"]

Bạn có thể điều khiển dấu phân cách bằng tham số chuỗi truy vấn của dấu phân cách. tức là, dấu phân cách = @, ví dụ: https://friendly-project-name.kinlan.now.sh/api/names?separator=@

["handsomely@asterisk"]

Một khía cạnh rất hữu ích của dự án này là nếu sự kết hợp của các từ có xu hướng gây khó chịu, thì có thể dễ dàng cập nhật repo Glitch để đảm bảo rằng nó không xảy ra lần nữa.

Giả sử rằng việc lưu trữ dự án không quá tốn kém, tôi sẽ giữ dịch vụ này, nhưng cứ tự nhiên sao chép nó nếu bạn muốn tạo ra một dịch vụ vi mô tương tự.

Ví dụ trực tiếp ###

Dưới đây là một ví dụ siêu nhanh về API đang hoạt động.

const render = (promise, elementId) => {
  promise.then(async(response) => {
    const el = document.getElementById(elementId);
    el.innerText = await response.text();
  })
};


onload = () => {
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names"), "basic");
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names?count=100"), "many");
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names?separator=@"), "separator");
}

Phản hồi đơn


Nhiều phản hồi


phân cách tùy chỉnh


{{<thô-html>}}

{{</ raw-html>}}

Frankie and Bennys: Pay for your meal via the web

Paul Kinlan

Bất cứ khi nào tôi thấy một nhà hàng nói rằng bạn có thể thanh toán trên điện thoại di động, tôi luôn kiểm tra nó, chủ yếu để tôi có thể nhận ra sự thật rằng bạn cần sử dụng một ứng dụng. Hãy tưởng tượng sự ngạc nhiên của tôi khi mã QR dẫn đến một luồng thanh toán dựa trên web ..... và nó đã hoạt động.

Read More

Podroll

Adding "dark mode" to my blog

Paul Kinlan

Tôi đã thấy post about adding dark mode to his blog của Jeremy Keith và nó có vẻ đơn giản, vì vậy tôi quyết định cho nó một vòng xoáy. Đây là diff of the work cho tất cả mọi người xem. Đó là dễ dàng đáng ngạc nhiên (bên ngoài các lỗi ngớ ngẩn về phía tôi). Có một bộ tái cấu trúc nhỏ để hỗ trợ các biến CSS và đảm bảo tôi có dự phòng nếu có một trình duyệt không hỗ trợ các thuộc tính tùy chỉnh CSS, nhưng đó là về nó.

Read More

Using Web Mentions in a static site (Hugo)

Paul Kinlan

Blog của tôi là một trang hoàn toàn tĩnh, được xây dựng với Hugo và được lưu trữ với Zeit. Đây là một giải pháp tuyệt vời cho tôi, một blog đơn giản có quy trình triển khai khá đơn giản và nó tải rất nhanh. Các trang web được tạo tĩnh có một số nhược điểm, lớn nhất là khi bạn cần bất kỳ thứ gì động để được tích hợp vào trang của bạn (ví dụ như nhận xét).

Read More

Creating a pop-out iframe with adoptNode and "magic iframes"

Paul Kinlan

Cập nhật ### : ngày 8 tháng 10 - Các vấn đề quan trọng với tài liệu này. Tôi đã bắt kịp với Jake Archibald về bài đăng này vì tôi nghĩ rằng tôi có một cuốn tiểu thuyết, trong cuộc trò chuyện, chúng tôi đã phát hiện ra rất nhiều điều làm cho một số bài đăng này không hợp lệ và tôi cũng đã học được rất nhiều trong quá trình mà tôi không nghĩ rằng hầu hết các nhà phát triển biết

Read More

Meatspace Augmented Reality: From Chester to Nagoya

Paul Kinlan

Some thoughts on AR after finding some during my travels. TL;DR - cheaper content creation and better discovery tools are needed.

Read More

Photos from Carlisle Castle

Paul Kinlan

Gần đây tôi đã đi nghỉ với các chàng trai và chúng tôi đã vượt qua Carlisle Castle (nơi sinh của thế giới hồi sinh Jake Archibald). Đó là một trong những lâu đài tốt hơn mà chúng tôi đã từng đến Vương quốc Anh và tôi chân thành khuyên bạn nên dành thời gian ghé thăm nó nếu bạn ở trong khu vực. Tôi không biết vai trò của Carlaus Castle đóng vai trò quan trọng như thế nào trong lịch sử của Anh và Scotland, và thật tuyệt khi tìm hiểu thêm trong khi chúng tôi ở đó.

Read More

Idle observation: Indexing text in images

Paul Kinlan

Tôi đã đi chơi với các chàng trai ở Llangollen vào một ngày khác (đó là một thị trấn xinh đẹp) và tôi đã chụp ảnh các dấu hiệu thông tin có chứa một số lịch sử của khu vực để tôi có thể đọc nó sau, và tôi nghĩ rằng tôi sẽ xem xét vào web để xem thông tin có sẵn cho nhiều người không chỉ là những người đi ngang qua biển báo - và không phải vậy.

Read More

Liverpool World Museum

Paul Kinlan

Tôi đã đưa bọn trẻ đến Bảo tàng Lịch sử Thế giới Liverpool vào tuần khác, nó khá gọn gàng. Phần Không gian và Thời gian đã không thay đổi trong khoảng 30 năm, một phần lớn của bao vây Bug đã bị đóng cửa và Thủy cung dường như nhỏ hơn tôi nhớ. Phần Ai Cập đã mở (không phải là lần cuối tôi đi) và nó khá tuyệt vời.

Read More

Bookstore - Llangollen

Paul Kinlan

Tôi yêu nơi này, nó nằm trên một quán cà phê ở Llangollen. Tôi đến đây với ông bà của tôi gần 30 năm trước và bây giờ cũng khá giống như vậy. Mong ước duy nhất của tôi là còn có nhiều sách Commix hơn nữa - tôi thề có một đống hơn khi tôi còn là một đứa trẻ. Check it out

Read More

Webmention.app🔗

Paul Kinlan

Tôi thích ý tưởng của Webmentions , nhưng tôi chưa có thời gian để thực hiện nó trên trang web của mình. Tại một trang web cấp cao đề cập đến cho phép bạn nhận xét, thích và trả lời nội dung khác trên web và hiển thị nội dung đó mà không bị tập trung với các công cụ như Disqus (mà tôi muốn xóa khỏi trang web của tôi).

Web Mentions được chia thành hai thành phần, người gửi và người nhận. Người nhận là trang web mà tôi đang viết một bài đăng và họ có thể có một cái gì đó trên trang web của họ hiển thị các liên kết hoặc phản ứng gửi đến blog của họ; và người gửi là, tốt, tôi. Tôi cần để trang web từ xa mà tôi đã viết hoặc phản ứng với một số nội dung mà họ đã tạo.

Remy Sharp khá tuyệt vời đã tạo ra webmention.app để giải quyết một phần của vấn đề: gửi ping. Công cụ của Remy giúp dễ dàng gửi 'ping' đến các máy thu tiềm năng mà tôi đã liên kết đến, chỉ bằng cách gọi một tập lệnh CLI.

Tôi lưu trữ blog của mình bằng Zeit bằng Hugo và công cụ xây dựng tĩnh, vì vậy đó là relatively trivial for me to add in support for webmention app . Tôi chỉ npm i webmention và sau đó gọi phiên bản CLI của công cụ từ tệp build.sh của tôi - nó thực sự đơn giản.

Bây giờ khi tôi tạo một bài đăng, nó sẽ gửi một ping nhanh đến tất cả các URL mới mà tôi đã tạo một số nội dung về trang web của họ.

Creating a commit with multiple files to Github with JS on the web

Paul Kinlan

Trang web của tôi là entirely static . Nó được xây dựng với Hugo và được lưu trữ với Zeit . Tôi khá hài lòng với thiết lập, tôi nhận được gần các bản dựng tức thì và phân phối nội dung CDN siêu nhanh và tôi có thể làm tất cả những việc tôi cần vì tôi không phải quản lý bất kỳ trạng thái nào. Tôi đã tạo một simple UI cho trang web này và cả podcast creator của tôi cho phép tôi nhanh chóng đăng nội dung mới lên trang web được lưu trữ tĩnh của mình.

Read More

Screen Recorder: recording microphone and the desktop audio at the same time🔗

Paul Kinlan

Tôi có một mục tiêu là xây dựng phần mềm ghi màn hình đơn giản nhất thế giới và tôi đã dần dần loay hoay trong dự án trong vài tháng qua (ý tôi là rất chậm).

Trong các bài viết trước, tôi đã có được screen recording and a voice overlay bằng cách kết hợp với các luồng từ tất cả các nguồn đầu vào. Mặc dù vậy, một điều khiến tôi thất vọng là tôi không thể tìm ra cách lấy âm thanh từ máy tính để bàn * và * phủ âm thanh từ loa. Cuối cùng tôi đã tìm ra cách để làm điều đó.

Thứ nhất, getDisplayMedia trong Chrome hiện cho phép thu âm thanh, có vẻ như có một sự giám sát kỳ lạ trong Spec ở chỗ nó không cho phép bạn chỉ định audio: true trong lệnh gọi chức năng, bây giờ bạn có thể.

const audio = audioToggle.checked || false;
desktopStream = await navigator.mediaDevices.getDisplayMedia({ video:true, audio: audio });

Thứ hai, ban đầu tôi đã nghĩ rằng bằng cách tạo hai bản nhạc trong luồng âm thanh, tôi sẽ có thể có được những gì tôi muốn, tuy nhiên tôi đã học được rằng API MediaRecorder của Chrome chỉ có thể tạo ra một bản nhạc và thứ 2, dù sao nó cũng không hoạt động vì các bản nhạc giống như các bản nhạc âm thanh đa dạng DVD trong đó mỗi lần chỉ có thể phát một bản nhạc.

Giải pháp có thể đơn giản với nhiều người, nhưng nó mới đối với tôi: Sử dụng Web Audio.

Hóa ra API WebAudio có createMediaStreamSourcecreateMediaStreamDestination , cả hai đều là API cần thiết để giải quyết vấn đề. createMediaStreamSource có thể lấy các luồng từ âm thanh và micrô trên máy tính để bàn của tôi và bằng cách kết nối hai luồng với nhau thành đối tượng được tạo bởi createMediaStreamDestination nó cho tôi khả năng chuyển một luồng này vào API MediaRecorder .

const mergeAudioStreams = (desktopStream, voiceStream) => {
  const context = new AudioContext();
    
  // Create a couple of sources
  const source1 = context.createMediaStreamSource(desktopStream);
  const source2 = context.createMediaStreamSource(voiceStream);
  const destination = context.createMediaStreamDestination();
  
  const desktopGain = context.createGain();
  const voiceGain = context.createGain();
    
  desktopGain.gain.value = 0.7;
  voiceGain.gain.value = 0.7;
   
  source1.connect(desktopGain).connect(destination);
  // Connect source2
  source2.connect(voiceGain).connect(destination);
    
  return destination.stream.getAudioTracks();
};

Đơn giản.

Mã đầy đủ có thể được tìm thấy trên my glitch và bản demo có thể được tìm thấy ở đây: https://screen-record-voice.glitch.me/

{{<nhanh-youtube oGIdqcMFKlA>}}

Extracting text from an image: Experiments with Shape Detection🔗

Paul Kinlan

Tôi đã có một chút thời gian sau khi Google IO và tôi muốn gãi ngứa lâu dài. Tôi chỉ muốn có thể sao chép văn bản được lưu giữ bên trong hình ảnh trong trình duyệt. Đó là tất cả. Tôi nghĩ rằng nó sẽ là một tính năng gọn gàng cho tất cả mọi người.

Thật không dễ dàng để thêm chức năng trực tiếp vào Chrome, nhưng tôi biết tôi có thể tận dụng hệ thống ý định trên Android và bây giờ tôi có thể làm điều đó với Web (hoặc ít nhất là Chrome trên Android).

Hai bổ sung mới cho nền tảng web - Chia sẻ Mục tiêu cấp 2 (hoặc theo cách tôi muốn gọi là Chia sẻ tệp) và TextDetector trong API phát hiện hình dạng - have allowed me to build a utility that I can Share images to and get the text held inside them .

Việc triển khai cơ bản tương đối đơn giản về phía trước, bạn tạo một Target Target và một trình xử lý trong Worker Worker, và sau đó khi bạn có hình ảnh mà người dùng đã chia sẻ, bạn chạy TextDetector trên nó.

Share Target API cho phép ứng dụng web của bạn là một phần của hệ thống con chia sẻ riêng và trong trường hợp này bạn có thể đăng ký để xử lý tất cả các loại image/* bằng cách khai báo bên trong Web App Manifest như sau.

"share_target": {
  "action": "/index.html",
  "method": "POST",
  "enctype": "multipart/form-data",
  "params": {
    "files": [
      {
        "name": "file",
        "accept": ["image/*"]
      }
    ]
  }
}

Khi PWA của bạn được cài đặt thì bạn sẽ thấy nó ở tất cả những nơi bạn chia sẻ hình ảnh như sau:

API Share Target xử lý các tệp chia sẻ như một bài đăng mẫu. Khi tệp được chia sẻ với Ứng dụng web, nhân viên dịch vụ được kích hoạt trình xử lý fetch được gọi với dữ liệu tệp. Bây giờ dữ liệu nằm trong Công nhân dịch vụ nhưng tôi cần nó trong cửa sổ hiện tại để tôi có thể xử lý nó, dịch vụ biết cửa sổ nào đã yêu cầu, vì vậy bạn có thể dễ dàng nhắm mục tiêu máy khách và gửi dữ liệu.

self.addEventListener('fetch', event => {
  if (event.request.method === 'POST') {
    event.respondWith(Response.redirect('/index.html'));
    event.waitUntil(async function () {
      const data = await event.request.formData();
      const client = await self.clients.get(event.resultingClientId || event.clientId);
      const file = data.get('file');
      client.postMessage({ file, action: 'load-image' });
    }());
    
    return;
  }
  ...
  ...
}

Khi hình ảnh nằm trong giao diện người dùng, tôi sẽ xử lý nó bằng API phát hiện văn bản.

navigator.serviceWorker.onmessage = (event) => {  
  const file = event.data.file;
  const imgEl = document.getElementById('img');
  const outputEl = document.getElementById('output');
  const objUrl = URL.createObjectURL(file);
  imgEl.src = objUrl;
  imgEl.onload = () => {
    const texts = await textDetector.detect(imgEl);
    texts.forEach(text => {
      const textEl = document.createElement('p');
      textEl.textContent = text.rawValue;
      outputEl.appendChild(textEl);
    });
  };
  ...
};

Vấn đề lớn nhất là trình duyệt không xoay hình ảnh một cách tự nhiên (như bạn có thể thấy bên dưới) và API phát hiện hình dạng cần văn bản ở hướng đọc chính xác.

Tôi đã sử dụng khá dễ dàng để sử dụng EXIF-Js library để phát hiện xoay và sau đó thực hiện một số thao tác canvas cơ bản để định hướng lại hình ảnh.

EXIF.getData(imgEl, async function() {
  // http://sylvana.net/jpegcrop/exif_orientation.html
  const orientation = EXIF.getTag(this, 'Orientation');
  const [width, height] = (orientation > 4) 
                  ? [ imgEl.naturalWidth, imgEl.naturalHeight ]
                  : [ imgEl.naturalHeight, imgEl.naturalWidth ];

  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext('2d');
  // We have to get the correct orientation for the image
  // See also https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
  switch(orientation) {
    case 2: context.transform(-1, 0, 0, 1, width, 0); break;
    case 3: context.transform(-1, 0, 0, -1, width, height); break;
    case 4: context.transform(1, 0, 0, -1, 0, height); break;
    case 5: context.transform(0, 1, 1, 0, 0, 0); break;
    case 6: context.transform(0, 1, -1, 0, height, 0); break;
    case 7: context.transform(0, -1, -1, 0, height, width); break;
    case 8: context.transform(0, -1, 1, 0, 0, width); break;
  }
  context.drawImage(imgEl, 0, 0);
}

Và Voila, nếu bạn chia sẻ một hình ảnh cho ứng dụng, nó sẽ xoay hình ảnh và sau đó phân tích nó trả về đầu ra của văn bản mà nó đã tìm thấy.

Thật thú vị khi tạo ra thử nghiệm nhỏ này, và nó ngay lập tức hữu ích cho tôi. Tuy nhiên, nó làm nổi bật inconsistency of the web platform . Các API này không có sẵn trong tất cả các trình duyệt, chúng thậm chí không có sẵn trong tất cả các phiên bản Chrome - điều này có nghĩa là khi tôi viết bài viết này về Chrome OS, tôi không thể sử dụng ứng dụng, nhưng đồng thời, khi tôi có thể sử dụng nó ... OMG, thật tuyệt.

Wood Carving found in Engakuji Shrine near Kamakura

Sakura

Paul Kinlan

Tôi nói cụ thể hơn rằng đây là 'Yaezakura'

Read More