Собираем свой веб-браузер из готовых элементов за 20 минут
hard

Собираем свой веб-браузер из готовых элементов за 20 минут

Сможете писать в портфолио, что создали свой браузер :-)

В прошлый раз мы разобрали, как устроены браузеры и что нужно для создания веб-браузера. Теперь соберём свой браузер из готовых компонентов и пойдём по пути Кристофера Нолана: сделаем страницу, которая будет работать как браузер, и превратим эту страницу в отдельный браузер. Браузер внутри браузера внутри фреймворка — всё как мы любим. В итоге получится вот такое:

Собираем свой веб-браузер из готовых элементов за 20 минут

Что делаем

Сначала мы напишем HTML-страницу, которая будет работать как браузер, а потом возьмём фреймворк ElectronJS, чтобы превратить её в полноценное приложение. В Электроне много готовых компонентов и инструментов отладки для создания кроссплатформенных настольных приложений на HTML, CSS и JavaScript. Фреймворк объединяет движок браузера Chromium и библиотеку Node.js, так что, по сути, нужно только сделать интерфейс, а Electron сделает всё остальное. В итоге мы получим минимальную рабочую версию браузера.

Порядок работы будет такой:

  1. Установим Node.js и npm — его менеджер пакетов.
  2. Установим Electron.
  3. Создадим папку проекта.
  4. Добавим в неё файлы package.json, main.js, preload.js, index.html, style.css.
  5. Добавим в package.json описание проекта, а в раздел scripts — команду для запуска приложения.
  6. Сделаем интерфейс на index.html и добавим стили в style.css.
  7. Напишем код для создания основного окна приложения и загрузки в него файла index.html.
  8. Пропишем в preload.js скрипт для безопасного взаимодействия между основным процессом и рендер-процессом. Этот файл позволяет передавать функции и данные из основного процесса, где работает main.js, в рендер-процесс, где выполняется код HTML и JavaScript.

Теперь сделаем всё это по очереди.

Устанавливаем Node.js, npm и ElectronJS

Для начала нужно установить Node.js — для этого заходим на официальный сайт Node.js и следуем инструкциям для своей операционной системы.

👉 Node.js — это система, которая исполняет JavaScript отдельно от вашего браузера. Можно сказать, что это самостоятельная среда для выполнения JavaScript. В состав Node.js ещё входит npm — менеджер пакетов, который поможет нам со сборкой. Пакет — это маленькая служебная программа, к которой можно обращаться из своей основной программы за помощью в каких-то делах. Если нам нужен пакет, которого ещё нет на компьютере, его можно установить командой npm install <имя пакета>

После установки Node.js ставим ElectronJS. Для этого открываем командную строку и пишем такую команду:

npm install electron --save-dev

Она установит Электрон и скачает все нужные файлы на компьютер, чтобы не выкачивать их каждый раз из Сети.

Создаём папку проекта и добавляем файлы

Далее создаём папку проекта, где будут лежать все наши файлы, и инициализируем npm-проект. Инициализация — это как бы старт проекта, когда мы говорим системе, что мы хотим начать делать что-то новое, а она в ответ создаёт и наполняет разные служебные файлы. 

Один из таких файлов — package.json, в котором находятся метаданные о проекте и список зависимостей. Для инициализации проекта в терминале вводим команду:

npm init

После инициализации файл автоматически заполнится стартовыми данными, которые мы можем поправить при необходимости:

Собираем свой веб-браузер из готовых элементов за 20 минут

Теперь открываем в редакторе кода папку проекта и создаём в ней следующие файлы:

  • main.js — основной файл, который будет запускать приложение;
  • preload.js — файл для безопасного взаимодействия между основным процессом и рендер-процессом;
  • index.html — главная HTML-страница;
  • style.css — файл стилей.

Добавляем описание и команду

Открываем package.json и редактируем его — вводим название проекта, информацию об авторе и версиях. Сохраняем: файл package.json обновится, и система скажет нам об этом.

{
  "name": "kodium",
  "version": "1.0.0",
  "main": "main.js",
  "devDependencies": {
    "electron": "^latest",
  },
  "scripts": {
    "start": "electron main.js"
  },
  "description": "simple browser",
  "author": "Kristina",
  "license": "ISC"
}
Собираем свой веб-браузер из готовых элементов за 20 минут

Создаём разметку страницы

Наша задача здесь — наполнить HTML-страницу index.html всеми нужными элементами и подключёнными стилями и скриптами, чтобы можно было их позже написать и сразу использовать в проекте. Логически страница состоит из таких элементов:

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

Когда мы нажимаем кнопку, страница берёт адрес из поля ввода, загружает внутрь себя новую страницу и показывает её в основном контейнере. Получается страница внутри страницы: первая нужна для выбора адреса и навигации, а вторая — основной контент.

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Браузер «Кодиум»</title>
 
  <!-- Подключаем файл стилей -->
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- Навигационная панель, содержащая поле ввода URL и кнопку для перехода -->
  <div id="nav-bar">
    <!-- Поле ввода для URL-адреса -->
    <input type="text" id="url-bar" placeholder="Введите URL" />
    <!-- Кнопка для перехода по введённому URL -->
    <button id="go-button">Перейти</button>
  </div>
  <!-- Контейнер для отображения содержимого страницы -->
  <div id="browser-content">
    <!-- Содержимое будет подгружаться сюда -->
  </div>
    <!-- Скрипт для обработки нажатия кнопки и перехода по указанному URL -->
  <script>
    document.getElementById('go-button').addEventListener('click', () => {
      // Получаем значение, введённое в поле URL
      const url = document.getElementById('url-bar').value;
      // Перенаправляем текущий документ на введённый URL
      window.location.href = url;
    });
  </script>
</body>
</html>

Получилась простая форма для ввода URL в адресной строке — выглядит странно, потому что стилей пока нет:

Собираем свой веб-браузер из готовых элементов за 20 минут

Добавляем стили

Теперь переходим к стилям — открываем в той же папке проекта файл style.css и начинаем визуальную магию:

  • устанавливаем отступы и поля;
  • настраиваем шрифты;
  • определяем, где какое должно быть выравнивание;
  • говорим, как выглядит и ведёт себя кнопка перехода при наведении;
  • приписываем, как располагать загруженную страницу внутри основного контейнера.

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

Запишем всё это на языке CSS:

/* Сброс отступов и полей, установка высоты на 100% для html и body */
body,
html {
  margin: 0; /* Убираем отступы */
  padding: 0; /* Убираем поля */
  height: 100%; /* Высота на 100% от высоты окна */
  font-family: Arial, sans-serif; /* Устанавливаем шрифт Arial для всего текста */
  box-sizing: border-box; /* Используем border-box для всех элементов */
}

/* Поле ввода URL */
#url-bar {
  width: 80%; /* Ширина поля ввода — 80% от ширины контейнера */
  padding: 10px; /* Внутренние отступы */
  border-radius: 64px; /* Скругляем углы */
}

/* Навигационная панель */
#nav-bar {
  display: flex; /* Используем флексбокс для выравнивания содержимого */
  align-items: center; /* Вертикальное выравнивание элементов по центру */
  width: 100vw; /* Ширина на всю ширину окна */
  height: 50px; /* Высота */
  background: #6780ee; /* Цвет фона */
}

/* Кнопка перехода */
#go-button {
  padding: 10px; /* Внутренние отступы */
  background-color: #d6fb51; /* Цвет фона */
  border-radius: 64px; /* Скругляем углы */
  color: #1b2b4c; /* Цвет текста */
  margin-left: auto; /* Вырыванием кнопку по правой часть панели */
}
/* Ховер для кнопки */
#go-button:hover {
  cursor: pointer; /* Изменяем курсор на указатель */
  background-color: #1b2b4c; /* Меняем цвет фона */
  color: #f5f6f7; /* Меняем цвет текста */
}

/* Контейнер для отображения загруженной страницы */
#browser-content {
  margin-top: 60px; /* Отступ сверху для размещения под панелью навигации */
  padding: 10px; /* Внутренние отступы для контента */
}

Вот что получилось после стилизации:

Собираем свой веб-браузер из готовых элементов за 20 минут

Пишем скрипты

Чтобы проект заработал, нам осталось написать два скрипта — main.js и preload.js

main.js

Скрипт main.js отвечает за основной процесс, который создаёт окна приложения и управляет их поведением. А ещё он определяет, какие файлы должны быть загружены в рендер-процессе при сборке и запуске полноценного приложения.

// Импортируем модули из Electron
const { app, BrowserWindow, ipcMain } = require("electron");
// Импортируем модуль path для работы с файловыми путями
const path = require("path");
// Переменная для хранения ссылки на главное окно приложения
let mainWindow;
// Функция для создания главного окна приложения
function createWindow() {
 // Создаём новое окно браузера с заданными параметрами
 mainWindow = new BrowserWindow({
   // Ширина окна
   width: 800,
   // Высота окна
   height: 600,
   webPreferences: {
     // Подключаем preload.js для безопасного взаимодействия
     preload: path.join(__dirname, "preload.js"),
     // Включаем изоляцию контекста для безопасности

     contextIsolation: true,
     // Отключаем удалённый модуль для дополнительной безопасности

     enableRemoteModule: false,
   },
 });

 // Загружаем файл index.html в окно
 mainWindow.loadFile("index.html");
 // Отображаем окно, когда оно будет готово
 mainWindow.once("ready-to-show", () => {
   mainWindow.show();
 });
 // Обрабатываем событие открытия URL из рендер-процесса
 ipcMain.on("open-url", (event, url) => {
   // Загружаем указанный URL в текущее окно
   mainWindow.loadURL(url);
 });
}
// Событие "ready" возникает, когда Electron готов создавать окна
app.on("ready", createWindow);
// Закрываем приложение, если все окна закрыты, кроме macOS
app.on("window-all-closed", () => {
 if (process.platform !== "darwin") {
   // Завершаем работу приложения
   app.quit();
 }
});
// Восстанавливаем окно, если приложение активируется без открытых окон
app.on("activate", () => {
 if (BrowserWindow.getAllWindows().length === 0) {
   // Создаём новое окно, если нет открытых окон
   createWindow();
 }
});

preload.js

С рендер-процессом связан файл preload.js. Этот файл обеспечивает безопасное взаимодействие между основным процессом и тем, как всё это отрисовывается на экране. Иногда можно обойтись без этого файла, но так как у нас внутри Электрона будет показываться сложное визуальное содержимое разных сайтов, лучше сделаем и добавим в проект.

// Импортируем нужные модули из Electron
const { contextBridge, ipcRenderer } = require("electron");
// Используем contextBridge для безопасного взаимодействия между рендер-процессом и основным процессом
contextBridge.exposeInMainWorld("electronAPI", {
 // Создаём функцию openURL, доступную в рендер-процессе, которая отправляет сообщение в основной процесс
 openURL: (url) => ipcRenderer.send("open-url", url),
});

Запускаем браузер

Чтобы запустить браузер, запускаем терминал, переходим в папку проекта и вводим команду npm start. Запустится приложение Electron, и мы увидим окно нашего браузера:

Собираем свой веб-браузер из готовых элементов за 20 минут

Вводим адрес в адресной строке и проверяем, что всё работает:

Собираем свой веб-браузер из готовых элементов за 20 минут

Что дальше

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

<!DOCTYPE html>
<html lang="ru">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Браузер «Кодиум»</title>
 <link rel="stylesheet" href="style.css">
</head>

<body>
 <div id="nav-bar">
   <input type="text" id="url-bar" placeholder="Введите URL" />
   <button id="go-button">Перейти</button>
 </div>
 <div id="browser-content">

 </div>
 <script>
   document.getElementById('go-button').addEventListener('click', () => {
     const url = document.getElementById('url-bar').value;
     window.location.href = url;
   });
 </script>
</body>

</html>

body,
html {
 margin: 0;
 padding: 0;
 height: 100%;
 font-family: Arial, sans-serif;
 box-sizing: border-box;
}

#url-bar {
 width: 80%;
 padding: 10px;
 border-radius: 64px;
}

#nav-bar {
 display: flex;
 align-items: center;
 width: 100vw;
 z-index: 1000;
 height: 50px;
 background: #6780ee;
}

#go-button {
 padding: 10px;
 background-color: #d6fb51;
 border-radius: 64px;
 color: #1b2b4c;
 margin-left: auto;
}

#go-button:hover {
 cursor: pointer;
 background-color: #1b2b4c;
 color: #f5f6f7;
}

#browser-content {
 margin-top: 60px;
 padding: 10px;
}

// Импортируем модули из Electron
const { app, BrowserWindow, ipcMain } = require("electron");
// Импортируем модуль path для работы с файловыми путями
const path = require("path");
// Переменная для хранения ссылки на главное окно приложения
let mainWindow;
// Функция для создания главного окна приложения
function createWindow() {
 // Создаём новое окно браузера с заданными параметрами
 mainWindow = new BrowserWindow({
   // Ширина окна
   width: 800,
   // Высота окна
   height: 600,
   webPreferences: {
     // Подключаем preload.js для безопасного взаимодействия
     preload: path.join(__dirname, "preload.js"),
     // Включаем изоляцию контекста для безопасности

     contextIsolation: true,
     // Отключаем удалённый модуль для дополнительной безопасности

     enableRemoteModule: false,
   },
 });

 // Загружаем файл index.html в окно
 mainWindow.loadFile("index.html");
 // Отображаем окно, когда оно будет готово
 mainWindow.once("ready-to-show", () => {
   mainWindow.show();
 });
 // Обрабатываем событие открытия URL из рендер-процесса
 ipcMain.on("open-url", (event, url) => {
   // Загружаем указанный URL в текущее окно
   mainWindow.loadURL(url);
 });
}
// Событие "ready" возникает, когда Electron готов создавать окна
app.on("ready", createWindow);
// Закрываем приложение, если все окна закрыты, кроме macOS
app.on("window-all-closed", () => {
 if (process.platform !== "darwin") {
   // Завершаем работу приложения
   app.quit();
 }
});
// Восстанавливаем окно, если приложение активируется без открытых окон
app.on("activate", () => {
 if (BrowserWindow.getAllWindows().length === 0) {
   // Создаём новое окно, если нет открытых окон
   createWindow();
 }
});

// Импортируем нужные модули из Electron
const { contextBridge, ipcRenderer } = require("electron");
// Используем contextBridge для безопасного взаимодействия между рендер-процессом и основным процессом
contextBridge.exposeInMainWorld("electronAPI", {
 // Создаём функцию openURL, доступную в рендер-процессе, которая отправляет сообщение в основной процесс
 openURL: (url) => ipcRenderer.send("open-url", url),
});

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Мария Климентьева

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
hard