Рубрики
Без рубрики

Как я построил библиотеку SiriwaveJs: посмотрите на математику и код

Flavio de Stefano, как я построил библиотеку SiriwaveJs: посмотрите на математику и кодовую тему было 4 года назад, когда у меня была идея, когда у меня была идея воспроизвести волновую форму Apple® Siri (введенную с iPhone 4S) в браузере, используя чистый JavaScript Отказ В течение последнего месяца я обновил

Автор оригинала: FreeCodeCamp Community Member.

Flavio de Stefano

Это было 4 года назад, когда я провел идею повторить Apple® Siri волновая форма (Введен с iPhone 4S) в браузере с использованием чистого JavaScript.

В течение последнего месяца я обновил эту библиотеку, сделав много рефакторинга с использованием функций ES6 и проверил процесс сборки, используя Rollupjs. Теперь я решил поделиться тем, что я узнал во время этого процесса, и математика за этой библиотекой.

Чтобы получить представление о том, что будет вывод, посетите Живой пример ; Весь кодовая база здесь Отказ

Кроме того, вы можете скачать все участки, нарисованные в этой статье в GCX (формат osx graphor): default.gcx и ios9.gcx Отказ

Классический стиль волны

Первоначально эта библиотека имела только классическую волновую форму, которую вы помните, используя в iOS 7 и iOS 8.

Не трудно повторить эту простовую волновую форму, только немного математических и основных концепций Canvas API.

Вы, вероятно, думаете, что волновая форма – это модификация Синус Математическое уравнение, а ты прав … Ну, почти правильно.

Перед началом кодирования мы должны найти наше линейное уравнение, которое будет просто применено впоследствии. Мой любимый редактор сюжета – Старинный Вы можете найти его в любой установке OSX под Приложения> Утилиты> Графа R.app.

Мы начинаем, рисуя хорошо известное:

Perfecto! Теперь добавим несколько параметров (амплитуда [a] , временная координата [t] и пространственная частота [k] ), которые будут полезны позже (читать дальше здесь: https://en.wikipedia.org/wiki/wave ).

Теперь мы должны «ослабить» эту функцию на границах сюжета, так что для | x |. > ; 2, т Он Y Значения имеют значение 0. Давайте нарисуем отдельно равносильно На G ( x), имеющие эти характеристики.

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

Теперь, умножая наше f (x, …) и G (x, …) , И путем установки точных параметров к другим статическим значениям, мы получаем что-то вроде этого.

  • A.9 Установите амплитуду волны на MAX
  • к Установите пространственную частоту, и получаем «больше пиков» в диапазоне [-2, 2]
  • t = -π/2 Установите перевод фазы, чтобы f (0,
  • К Установите фактор для «уравнения ослабления», так что окончательное уравнение, когда | x |. ≥ 2.

Это выглядит хорошо! ?

Теперь, если вы заметите на оригинальной волне, у нас есть другие подза-волны, которые дадут более низкое значение для амплитуды. Давайте нарисуем их за A = {0,8, 0,6, 0,4, 0,2, -0,2, -0,4, -0,6, -0,8}

В финальной композиции холста по подзаголовьям будут нарисованы под снижению непрозрачности до 0.

Основные концепции кода

Что мы сейчас делаем с этим уравнением?

Мы используем уравнение для получения Y Value для вход X Отказ

В основном, используя простой для петли от -2 до 2, (границы графика в этом случае) мы должны рисовать указать на точку Уравнение на холсте, используя rightpath и Ленто API.

const ctx = canvas.getContext('2d');
ctx.beginPath();ctx.strokeStyle = 'white';
for (let i = -2; i <= 2; i += 0.01) {   const x = _xpos(i);   const y = _ypos(i);   ctx.lineTo(x, y);}
ctx.stroke();

Вероятно, этот псевдокод очистит эти идеи. Мы все еще должны реализовать наши _xpos и _YPOS Функции.

Но … Эй, что такое 0,01⁉️ Это значение представляет Сколько пикселей Вы движетесь вперед в каждой итерации, прежде чем достичь правой границы сюжета … Но какое правильное значение?

Если вы используете действительно небольшое значение ( <0. 01), вы получите безумно точное рендеринг графика, но ваша производительность уменьшится, потому что вы получите слишком много итераций.

Вместо этого, если вы используете действительно большое значение ( > 0. 1) Ваш график потеряет точность, и вы заметите это мгновенно.

Вы можете видеть, что последний код фактически похож на псевдо-код: https://github.com/kopiro/siriwave/blob/master/src/curve.js#l25.

Внедрить _xpos (i)

Вы можете утверждать, что если мы рисуем сюжет, увеличивая х Тогда _xpos Может просто вернуть аргумент ввода.

Это почти правильно, но наш участок всегда набирается из -B к B (Б).

Итак, чтобы нарисовать на холсте через координаты пикселей Мы должны перевести -B до 0, и B до 1 (простая транспозиция [-b, b] на [0,1]); Затем умножьте [0,1] и Ширина холста (W).

https://github.com/kopiro/siriwave/blob/master/src/curve.js#L19

Реализовать _ypos

Реализовать _YPOS Мы должны просто написать наше уравнение, полученное ранее (близко).

const K = 4;const FREQ = 6;
function _attFn(x) {   return Math.pow(K / (K + Math.pow(x, K)), K);}
function _ypos(i) {   return Math.sin(FREQ * i - phase) *       _attFn(i) *       canvasHeight *      globalAmplitude *       (1 / attenuation);}

Давайте уточним некоторые параметры.

  • Canwasheight Высота холста выражена в PX
  • Я Это наше входное значение ( x )
  • фаза является наиболее важным параметром, давайте обсудим это позже
  • GlobalAmPlitude является статическим параметром, представляющим амплитуду общей волны (состоит из подза-волн)
  • ослабление это статический параметр, который меняется для каждой строки и представляет амплитуду волны

https://github.com/kopiro/siriwave/blob/master/src/curve.js#L24

Фаза

Теперь давайте обсудим о Переменная фазы: Это Первая смена переменной Со временем, потому что это имитирует волновое движение.

Что это значит? Это значит, что для каждого кадр анимации, Наш базовый контроллер должен увеличение Это значение. Но, чтобы избежать этого значения, бросая переполнение буфера, давайте модуль его с 2π (поскольку Math.sin Домио уже модуль 2π).

phase = (phase + (Math.PI / 2) * speed) % (2 * Math.PI);

Умножем Скорость и Math.pi Так что с Скорость У нас есть максимальная скорость (почему? Потому что грех (0) ,, …?)

Доработка

Теперь, когда у нас есть все код, чтобы нарисовать одну строку, мы определяем массив конфигурации, чтобы нарисовать все подза-волны, а затем циклы над ними.

return [   { attenuation: -2, lineWidth: 1.0, opacity: 0.1 },   { attenuation: -6, lineWidth: 1.0, opacity: 0.2 },   { attenuation: 4, lineWidth: 1.0, opacity: 0.4 },   { attenuation: 2, lineWidth: 1.0, opacity: 0.6},
   // basic line   { attenuation: 1, lineWidth: 1.5, opacity: 1.0},];

https://github.com/kopiro/siriwave/blob/master/src/siriwave.js#L190

Стиль iOS 9+

Теперь все начинают усложняться. Стиль, введенный с iOS 9, действительно сложный и обратный инжиниринг для их моделирования Вообще не легко ! Я не полностью удовлетворен окончательным результатом, но я буду продолжать ее улучшать, пока не получите желаемый результат.

Как ранее сделано, давайте начнем получать линейные уравнения волн.

Как вы можете заметить:

  • У нас есть три Различные зеркальные уравнения С разными цветами ( зеленый, синий, красный )
  • одна волна, кажется, является Сумма уравнений синуса с Разные параметры
  • Все остальные цвета – это Состав Из этих трех базовых цветов
  • Есть прямая линия на границах сюжета

Снова выбирая наши предыдущие уравнения, давайте определим более сложное уравнение, которое включает в себя перевод. Мы начинаем с определения нашего уравнения ослабления:

Теперь определите h (x, a, k, t) Функция, то есть Синусоидация умножено на Функция ослабления, В своем абсолютном значении:

Теперь у нас есть мощный инструмент.

С h (x) Теперь мы можем создать окончательную волновую форму, суммируя разные h (x) С разными параметрами, связанными с различными амплитудами, частотой и переводами. Например, давайте определим Красная кривая положив случайные значения.

Если мы сделаем то же самое с зеленый и синий Кривая, это результат:

Это не совсем идеально, но это может работать.

Чтобы получить зеркальную версию, просто умножьте все на -1.

В кодирующей стороне подход одинаково, у нас только более сложное уравнение для _Ипос.

const K = 4;const NO_OF_CURVES = 3;
// This parameters should be generated randomlyconst widths = [ 0.4, 0.6, 0.3 ];const offsets = [ 1, 4, -3 ];const amplitudes = [ 0.5, 0.7, 0.2 ];const phases = [ 0, 0, 0 ];
function _globalAttFn(x) {   return Math.pow(K / (K + Math.pow(x, 2)), K);}
function _ypos(i) {   let y = 0;   for (let ci = 0; ci < NO_OF_CURVES; ci++) {      const t = offsets[ci];      const k = 1 / widths[ci];      const x = (i * k) - t;            y += Math.abs(         amplitudes[ci] *          Math.sin(x - phases[ci]) *          _globalAttFn(x)      );   }
   y = y / NO_OF_CURVES;   return canvasHeightMax * globalAmplitude * y;}

Здесь ничего нет. Единственное, что изменилось, это то, что мы вело No_of_curves раз во всех псевдослучайных параметрах и мы сумма Все Y Значения.

Прежде чем умножить его для CanvasheightMax и GlobalAmPlitude которые дают нам абсолютную координату PX Canvas, мы разделяем его для no_of_curves, чтобы Y всегда ≤ 1.

https://github.com/kopiro/siriwave/blob/master/src/ios9curve.js#L103

Композитная операция

Одна вещь, которая на самом деле имеет значение здесь, это GlobalCompositeoperation Режим для установки в холсте. Если вы заметите, в исходном контроллере, когда есть перекрытие 2+ цветов, они фактически смешиваются стандартным способом.

По умолчанию установлено значение Источник – над , но результат плохой, даже с набором непрозрачности.

Вы можете увидеть все примеры варты GlobalCompositeoperation Вот: https://developer.mozilla.org/en-us/docs/web/api/canvasrenderingcontext2d/globalcompositeoperation.

Установка GlobalCompositeoperation к «Лингер» Вы замечаете, что пересечение цветов ближе к оригиналу.

Построить с Rollupjs

Перед рефакторингом все, я совсем не удовлетворен с кодовой базой: старых прототипов, подобных типам, похожим на все файл JavaScript для всего, нет ULIFY/Minify и Нет построения вообще.

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

Кроме того, я использовал Rollupjs Создать транспортированную и полезную сборку в различных форматах.

Поскольку это только библиотека только в браузере, я решил создать два сборка: An УМД (универсальный модуль определение) Создайте, что вы можете использовать напрямую, импортируя скрипт или с помощью CDN, а другой, как Модуль ESM.

Модуль UMD построен с этой конфигурацией:

{   input: 'src/siriwave.js',   output: {      file: pkg.unpkg,      name: pkg.amdName,      format: 'umd'    },    plugins: [       resolve(),       commonjs(),       babel({ exclude: 'node_modules/**' }),    ]}

Дополнительный Minified Umd модуль построен с этой конфигурацией:

{   input: 'src/siriwave.js',   output: {      file: pkg.unpkg.replace('.js', '.min.js'),      name: pkg.amdName,      format: 'umd'    },    plugins: [       resolve(),       commonjs(),       babel({ exclude: 'node_modules/**' }),       uglify()]}

Преимущество услуг UNPKG, вы можете найти окончательную сборку на этом URL, обслуживаемом CDN: https://unpkg.com/siriwave/dist/siriwave.min.js.

Это «Well Style JavaScript Way» – вы можете просто импортировать свой скрипт, а затем обратиться в свой код, используя Siriwave Глобальный объект.

Чтобы обеспечить более элегантный и современный путь, я также построил модуль ESM с этой конфигурацией:

{    input: 'src/siriwave.js',   output: {       file: pkg.module,       format: 'esm'   },    plugins: [       babel({ exclude: 'node_modules/**' })   ]}

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

Вы можете найти окончательную конфигурацию Rollupjs здесь: https://github.com/kopiro/siriwave/blob/master/rollup.config.js.

Смотреть и горячий код перезагрузки

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

По сути, вы просто добавляете эти плагины, когда вы находитесь в режиме «Разработчик»:

import livereload from 'rollup-plugin-livereload';import serve from 'rollup-plugin-serve';
if (process.env.NODE_ENV !== 'production') { additional_plugins.push(  serve({   open: true,   contentBase: '.'  }) ); additional_plugins.push(  livereload({   watch: 'dist'  }) );}

Мы заканчиваем, добавив эти строки в Package.json:

"module": "dist/siriwave.m.js","jsnext:main": "dist/siriwave.m.js","unpkg": "dist/siriwave.js","amdName": "SiriWave","scripts": {   "build": "NODE_ENV=production rollup -c",   "dev": "rollup -c -w"},

Давайте уточним некоторые параметры:

  • Модуль/jsnext: Главная: Путь DIST ESM модуль
  • UNPCG: Путь DIST UMD модуль
  • Amdname: Имя глобального объекта в модуле UMD

Спасибо много Rollupjs!

Надеюсь, что вы найдете эту статью интересную, увидимся в ближайшее время! ?