Hello.

I am Paul Kinlan.

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

Puppeteer Go

Paul Kinlan

Я люблю Puppeteer - он позволяет мне поиграть с идеями The Headless Web - который работает в Интернете в браузере без видимого браузера и даже создает инструменты, такие как DOM-curl (Curl, который запускает JavaScript). В частности, я люблю писать скрипты в браузере, чтобы очищать страницы, манипулировать ими и взаимодействовать с ними.

Одна демонстрация, которую я хотел сделать, была вдохновлена Capturing 422 live images где она запустила сценарий кукловода, который переместился на многие страницы и сделал снимок экрана. Вместо того, чтобы переходить на много страниц, я хотел сделать много скриншотов элементов на странице.

Проблема, с которой я столкнулся с Puppeteer, - это вступительная строфа, в которой вам нужно что-то делать Запустить, открыть вкладку, перемещаться - это не сложно, это всего лишь шаблон, который я хочу создать для простых скриптов. Вот почему я создал Puppeteer Go . Это всего лишь небольшой скрипт, который помогает мне легко создавать утилиты CLI, который открывает браузер, переходит на страницу, выполняет действие your, а затем убирает за собой.

Проверьте это.

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);
      }
    }
});

Приведенный выше код найдет элемент h1 в моем блоге и сделает снимок экрана. Это далеко не так хорошо, как работа Ire, но я подумал, что было бы неплохо посмотреть, сможем ли мы быстро получить скриншоты с canisuse.com прямо со страницы.

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

Наслаждайтесь!

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

A simple video insertion tool for EditorJS

Paul Kinlan

Мне очень нравится EditorJS . Это позволило мне создать очень простой веб-интерфейс для моего статичного блога Hugo.

EditorJS содержит большую часть того, что мне нужно, в простом блочном редакторе. Он имеет плагин для заголовков, кода и даже простой способ добавления изображений в редактор без необходимости размещения инфраструктуры. До сих пор не было простого способа добавить видео в редактор.

Я взял simple-image репозиторий плагин и изменил его (только чуть) , чтобы создать simple-video плагин ( npm module ). Теперь я могу легко включить видео в этот блог.

Если вы знакомы с EditorJS, это довольно просто включить в ваши проекты. Просто установите его следующим образом

npm i simple-video-editorjs

А затем просто включите его в свой проект, как считаете нужным.

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

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

В редакторе есть несколько простых опций, которые позволяют вам настроить способ размещения видео на странице:

  1. Автозапуск - будет ли видео воспроизводиться автоматически при загрузке страницы
  2. muted - по умолчанию звук в видео не будет включен (необходим для автозапуска)
  3. элементы управления - будут ли у видео стандартные элементы управления HTML.

Ниже приведен быстрый пример встроенного видео (и показаны некоторые параметры).

В любом случае, я получал удовольствие от создания этого маленького плагина - его было не так сложно создать, и единственное, что я сделал, - это отложил преобразование в base64, которое использует simple-images, и вместо этого просто использовал URL-адреса BLOB-объектов.

Test post Video upload

Paul Kinlan

Если вы видите видео здесь, то это сработало.

Read More

Friendly Project Name Generator with Zeit

Paul Kinlan

У меня есть несколько идей для проектов, которые облегчают создание сайтов в Интернете - одна из идей - создать netlify-like drag and drop interface для zeit основе zeit (мне нравится Zeit, но для его развертывания требуется немного магии).

Этот пост охватывает только один маленький кусочек головоломки: создание названий проектов.

Glitch - хороший пример этого, когда вы создаете проект, он дает ему причудливое случайно сгенерированное имя. Команда также создала good dictionary of fairly safe words который хорошо сочетается (и если вы хотите, чтобы у них был простой сервер для размещения).

Итак, побочный проект в это воскресенье должен был создать простой микро-сервис для генерации случайных имен проектов с использованием Zeit's serverless-functions и словаря из Glitch.

And here it is ( code ), он довольно короткий и не слишком сложный.

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 }

Если вы не хотите включать его в свой проект напрямую, вы можете использовать конечную точку HTTP для генерации случайных имен проектов (в форме «XY»), отправив веб-запрос на адрес https: // friendly-project-name. kinlan.now.sh/api/names, который будет возвращать что-то вроде следующего.

["momentous-professor"]

Вы также можете контролировать количество имен, генерируемых с помощью параметра строки запроса count = x , например, 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"]

Вы можете управлять разделителем с помощью параметра строки запроса разделителя. т.е. разделитель = @, например, https://friendly-project-name.kinlan.now.sh/api/names?separator=@

["handsomely@asterisk"]

Очень полезный аспект этого проекта заключается в том, что если комбинация слов имеет тенденцию быть оскорбительной, легко обновить репозиторий Glitch, чтобы избежать повторения.

Предполагая, что хостинг проекта не станет слишком дорогим, я буду поддерживать сервис, но не стесняйтесь клонировать его самостоятельно, если вы когда-нибудь захотите создать подобный микро-сервис.

Живой пример

Ниже приведен очень быстрый пример API в действии.

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");
}

Один ответ



Многие респонденты



Пользовательские разделители



{{<raw-html>}}

{{</ raw-html>}}

Frankie and Bennys: Pay for your meal via the web

Paul Kinlan

Всякий раз, когда я вижу, что в ресторане говорят, что вы можете платить по мобильному телефону, я всегда проверяю это, в основном, чтобы я мог оплакивать тот факт, что вам нужно использовать приложение. Вообразите мое удивление, когда QR-код привел к потоку платежей через Интернет ….. и это сработало. Отличная работа Фрэнки и Бенни! В этот момент я выбрал Google Pay, но он не работал (электронная почта отправлена!

Read More

Podroll

Paul Kinlan

Я люблю подкасты и слушаю довольно много, но я все еще нахожу, что обнаружение новых подкастов - довольно сложный процесс, и я полагаюсь на друзей, которые делятся тем, что слушают. Итак, с учетом этого, вот мои podroll . Я использую Player.fm созданный моим хорошим другом Майком Махемоффом и способный поделиться и экспортировать ваш список подписки. Эта страница будет refreshed frequently using my little script .

Read More

Adding "dark mode" to my blog

Paul Kinlan

Я видел « post about adding dark mode to his blog Джереми Кейта, и это казалось простым, поэтому я решил post about adding dark mode to his blog его. Вот diff of the work для diff of the work . Это было удивительно легко (за исключением глупых ошибок с моей стороны). Был небольшой рефакторинг для поддержки CSS-переменных и обеспечения того, чтобы у меня был запасной вариант, если есть браузер, который не поддерживает пользовательские свойства CSS, но это все.

Read More

Using Web Mentions in a static site (Hugo)

Paul Kinlan

Мой блог - полностью статичный сайт, созданный с помощью Hugo и размещенный на Zeit. Это отличное решение для меня, простой блог имеет довольно простой процесс развертывания и загружается невероятно быстро. Статически сгенерированные сайты имеют некоторые недостатки, самый большой из которых - когда вам нужно что-то динамическое для интеграции в вашу страницу (например, комментарии). Неспособность легко разместить динамический контент будет означать, что вы в конечном итоге будете полагаться на сторонний JavaScript, который затем получит полный доступ к вашей странице, и вы не будете знать, что он делает - это может быть отслеживание ваших пользователей или замедление вашей страницы нагрузить.

Read More

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

Paul Kinlan

Обновление ### : 8 октября - Значительные проблемы с этим документом. Я догнал Jake Archibald об этом посте, потому что я думал, что у меня есть что-то новое, во время разговора мы обнаружили много вещей, которые делают часть этого поста недействительной, и я также многому научился в процессе, который я не думаю, что большинство разработчиков знать. .append() и .appendChild() принимают узел. Это делает использование adoptNode в этом случае бесполезным, потому что алгоритм добавления гарантирует, что узел принят.

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

Недавно я отправился в отпуск с ребятами, и мы Carlisle Castle мимо Carlisle Castle (место рождения мирового отскока Джейка Арчибальда). Это один из лучших замков, в которых мы бывали в Великобритании, и я бы искренне рекомендовал потратить некоторое время на его посещение, если вы находитесь в этом районе. Я не знал, насколько значимой была роль Карлайлского замка в истории Англии и Шотландии, и было здорово узнать больше, пока мы были там.

Read More

Idle observation: Indexing text in images

Paul Kinlan

Я был с мальчиками в Llangollen на днях (это красивый город), и я снимал информационные знаки, содержащие некоторую историю этого района, чтобы я мог прочитать это позже, и я думал, что я буду смотреть на Интернет, чтобы увидеть, была ли информация доступна не только людям, проходящим мимо знака - и это не так. Затем я подумал о листовках, содержание которых в Интернете практически невозможно найти. Я не уверен, что многие люди заботятся о тексте на изображениях и делают его доступным для поисковых систем и пользователей, которые испытывают трудности при чтении, но это кажется довольно хорошей победой для привлечения большего количества контента в Интернет, а также для улучшения доступа к информации для все.

Read More

Liverpool World Museum

Paul Kinlan

На прошлой неделе я отвез детей в Музей всемирной истории Ливерпуля, там было довольно аккуратно. Раздел «Пространство и время» не изменился примерно за 30 лет, большая часть корпуса Буга была закрыта, и Аквариум казался немного меньше, чем я помню. Египетская секция была открыта (это было не тогда, когда я в последний раз ходил), и это было довольно круто.

Read More

Bookstore - Llangollen

Paul Kinlan

Мне нравится это место, оно находится над кафе в Лланголлен. Я приехал сюда с бабушкой и дедушкой почти 30 лет назад, и сейчас все почти так же. Мое единственное желание, чтобы было больше книг Commix - я клянусь, что было еще много, когда я был ребенком. Check it out

Read More

Webmention.app

Paul Kinlan

Мне нравится идея Webmentions , но у меня не было времени реализовать ее на моем сайте. На высокоуровневых веб-ссылках вы можете комментировать, ставить лайки и отвечать на другие материалы в Интернете и делать их видимыми для этого контента без централизации с помощью таких инструментов, как Disqus (которые я очень хочу удалить с моего сайта).

Веб-упоминания разделены на два компонента: отправитель и получатель. Получатель - это сайт, о котором я пишу сообщение, и у них может быть что-то на их сайте, которое показывает входящие ссылки или реакцию на их блог; и отправитель, ну, я. Мне нужно разрешить удаленному сайту, который я написал или отреагировал на какой-то контент, который они создали.

Довольно удивительный Remy Sharp создал webmention.app для решения одной части проблемы: отправки пингов. Инструмент Реми позволяет легко отправлять «пинги» потенциальным получателям, с которыми я связан, просто вызывая скрипт CLI.

Я веду свой блог, используя Zeit, используя Hugo и инструмент static-builder, так что это был relatively trivial for me to add in support for webmention app . Я просто npm i webmention а затем вызываю CLI-версию инструмента из моего файла build.sh - это действительно так просто.

Теперь, когда я создаю сообщение, оно должно отправлять быстрый пинг на все новые URL, которые я создал для своего сайта.

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

Paul Kinlan

Мой сайт entirely static . Он построен на Hugo и размещен на Zeit . Я очень доволен настройкой, у меня почти мгновенные сборки и сверхбыстрая доставка контента CDN, и я могу делать все, что мне нужно, потому что мне не нужно управлять каким-либо состоянием. Я создал simple UI для этого сайта, а также мой podcast creator который позволяет мне быстро публиковать новый контент на моем статически размещенном сайте. Так. Как я это сделал?

Read More

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

Paul Kinlan

У меня есть цель создать самое простое в мире программное обеспечение для записи экрана, и я последние несколько месяцев медленно слоняюсь по проекту (я имею в виду очень медленно).

В предыдущих постах я получил screen recording and a voice overlay , возившись с потоками из всех входных источников. Однако одной из проблем было то, что я не мог понять, как получить звук с рабочего стола * и * наложить звук из динамика. Я наконец-то понял, как это сделать.

Во-первых, getDisplayMedia в Chrome теперь позволяет захватывать звук, в Spec есть странный недосмотр, поскольку он не позволяет вам указывать audio: true в вызове функции, теперь вы можете это сделать.

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

Во-вторых, я изначально думал, что, создав две дорожки в аудиопотоке, я смогу получить то, что хотел, однако я узнал, что API-интерфейс MediaRecorder Chrome может выводить только одну дорожку, а во- MediaRecorder , это не сработало бы, потому что дорожки похожи на аудиофонограммы DVD, которые могут воспроизводить только одна.

Решение, вероятно, простое для многих людей, но для меня оно было новым: используйте Web Audio.

Оказывается, в API WebAudio есть createMediaStreamSource и createMediaStreamDestination , оба из которых необходимы API для решения проблемы. createMediaStreamSource может принимать потоки с моего настольного аудио и микрофона, и, соединяя их вместе в объект, созданный createMediaStreamDestination он дает мне возможность MediaRecorder этот поток в 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();
};

Simples.

Полный код можно найти на my glitch , а демоверсию можно найти здесь: https://screen-record-voice.glitch.me/

{{<fast-youtube oGIdqcMFKlA>}}

Extracting text from an image: Experiments with Shape Detection

Paul Kinlan

У меня было немного простоя после Google IO, и я хотел покончить с долговременным зудом, который у меня был. Я просто хочу иметь возможность копировать текст, который хранится внутри изображений в браузере. Вот и все. Я думаю, что это будет отличная особенность для всех.

Нелегко добавить функциональность непосредственно в Chrome, но я знаю, что могу воспользоваться преимуществами системы намерений на Android, и теперь я могу сделать это с помощью Интернета (или, по крайней мере, Chrome на Android).

Два новых дополнения к веб-платформе - Share Target Level 2 (или, как мне нравится называть это File Share) и TextDetector в API обнаружения формы - have allowed me to build a utility that I can Share images to and get the text held inside them .

Базовая реализация относительно проста: вы создаете Share Share Target и обработчик в Service Worker, а затем, когда у вас есть образ, которым поделился пользователь, вы запускаете TextDetector на нем.

Share Target API позволяет вашему веб-приложению быть частью собственной подсистемы совместного использования, и в этом случае вы теперь можете зарегистрироваться для обработки всех типов image/* , объявив его внутри Web App Manifest следующим образом.

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

Когда ваш PWA установлен, вы увидите его во всех местах, где вы обмениваетесь изображениями следующим образом:

API Share Target рассматривает совместное использование файлов как форму сообщения. Когда файл fetch в веб-приложение, сервисный работник активируется, и обработчик fetch вызывается с данными файла. Теперь данные находятся внутри Service Worker, но они мне нужны в текущем окне, чтобы я мог их обработать, служба знает, какое окно вызвало запрос, чтобы вы могли легко ориентироваться на клиента и отправлять ему данные.

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;
  }
  ...
  ...
}

Когда изображение находится в пользовательском интерфейсе, я обрабатываю его с помощью API обнаружения текста.

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);
    });
  };
  ...
};

Самая большая проблема заключается в том, что браузер не поворачивает изображение естественным образом (как вы можете видеть ниже), а API обнаружения формы необходимо, чтобы текст был в правильной ориентации чтения.

Я использовал довольно простой в использовании EXIF-Js library чтобы обнаружить вращение, а затем выполнить некоторые базовые манипуляции с холстом, чтобы переориентировать изображение.

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);
}

И вуаля, если вы поделитесь изображением с приложением, оно будет вращать изображение и затем анализировать его, возвращая вывод найденного текста.

Создать этот маленький эксперимент было невероятно весело, и он сразу пригодился мне. Это, однако, выделить inconsistency of the web platform . Эти API доступны не во всех браузерах, они недоступны даже во всех версиях Chrome - это означает, что, когда я пишу эту статью для Chrome OS, я не могу использовать приложение, но в то же время, когда я могу его использовать … О боже, так круто.

Wood Carving found in Engakuji Shrine near Kamakura

Sakura

Paul Kinlan

Мне сказали, что более конкретно, что это «Yaezakura»

Read More