Знакомство с famo.us js

5 July 2014 г. 22:32:05

Пришло время написать статью об одном интересном фреймворке который позволяет создавать интерактивные веб приложения, который работает во всех современных браузерах и мобильных платформах. Этот фреймворк называется famo.us.

Что же он позволяет делать? Он позволяет создавать приложения нового поколения, которые одинаково хорошо работают во всех браузерах и мобильных устройствах. Для вывода графики он использует 3d трансформации используюя CSS, WebGL или Canvas.  

Введение

Как ни странно но на сайте достаточно мало информации о том как создавать приложения. Документация автоматически сгенерирована из исходных кодов и не сильно помогает разобраться в многообразии фич доступных из библиотеки. Однако меня очень порадовал раздел university в котором в виде гайдов приведены основные подходы к разработке. От вывода простейших примитивов до проектирования интерфейсов. Стоит отметить, что на момент написания статьи не все разделы были доступны, однако для начала этого более чем достаточно. Сами гайды выполнены достаточно оригинально. Интерфейс разбит на 3 колонки, слева описание главы, посередине интерпритатор кода, который внешне очень напоминает мой любимый Sublime. И справа можно посмотреть что же получается в результате выполнения кода. Несмотря на то что сами уроки совсем короткие, я провел не мало времени играясь с кодом.

В бой.

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

Приступим. На сайте можно скачать архив famous-starter-kit, в котором вы найдете документацию и примеры как вывода примитивов, так и примеры интерфейсов и анимации. Код примеров достаточно прост и результат действительно впечатляет. Так же в папке Boilerplate Вы найдете шаблон в кортом подключены все необходимые для работы библиотеки и как раз в нем нам и предстоит разрабатывать наше первое приложение.

Начнем с вывода простейшего примитива - прямоугольника

создание любого приложения на famous начинается с создания контекста, контекст в свою очередь соединяется с контекстом документа и выводит в браузер результат, таким образом все примитивы и модификаторы будут добавляться в контекст

var Engine = require('famous/core/Engine'); var mainContext = Engine.createContext();

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

var Surface       = require('famous/core/Surface');
var firstSurface = new Surface({
    size: [200, 100],
    content: 'Hello Famo.us',
    properties: {
      color: 'white',
      textAlign: 'center',
      backgroundColor: '#FA5C4F'
    }
});

Теперь добавим первую поверхность в контекст и можно любоваться результатом

mainContext.add(firstSurface);

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

Для работы с модификаторами выполним импорт

var StateModifier = require('famous/modifiers/StateModifier');

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

В параметре transfotm указывает, тип трансформации, в данном случае перемещение, и указываем значение перемещения и координаты на которые надо сдвинуть относительно точки origin

var translate = new StateModifier({

      origin: [.5,.5], //выполняем трансформацию относительно центра

      transform: Transform.translate(100,100)

    })

Теперь чтобы применить транформацию, добавим ее в контекст.

mainContext.add(translate).add(firstSurface);

Аналогичным образом работает трансформация вращения

var rotate = new StateModifier({
  transform: Transform.rotateY(Math.PI / 4)
})

mainContext.add(translate).add(rotate).add(firstSurface);

Стоит отметить, что это далеко не полный перечень доступных трансформаций. В famous js доступна библиотека для работы с матрицами, поэтому для трансформации можно использовать матричные преобразования, но об этом в одной из следующей статей.

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

К поверхностям можно привязывать различные события, в частности click, mousemove, mouseover и т.д. Делается это очень просто. Чтобы связать поверхность с определенным событием используется функция on, в которую в качестве параметров передается имя события и функция обработчик.

surface.on('click', function() {
  //do the stuff
});

Давайте добавим немного анимации, чтобы по клику на поверхность выполняла вращение вокруг центра.

var angle = 0;

firstSurface.on("click", function(){
    angle = angle + Math.PI;
    rotate.setTransform(Transform.rotateZ(angle), {duration: 500});      
})

Рассмотрим добавим еще одну анимацию, по нажатию на поверхность она будет падать вниз или вверх.

    translate.setTransform(Transform.translate(0, 100 * updown, 0), {duration: 500})

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

Вот практически и все что понадобиться для начала. Теперь попробуем применить все знания на практике и создадим достаточно простую галерею.

Разработка галереии

Галерея будет выглядеть следующим образом - на переднем плане будет основное изображение, а далее слева и справа будут выступать более старые изображения. изображения будут заслонять друг друга, но при наведение мышки они будут “выглядывать”.

Так как мы будем использовать трехмерное пространство, то первым делом надо указать глубину перспективы.

mainContext.setPerspective(1000);

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

var Card = function(params){
  var self = this;
  this.getAngle     = (function(){ return params.angle })();
  this.getX         = (function(){ return params.x })();
  this.getY         = (function(){ return params.y })();
  this.getZ         = (function(){ return params.z })();
  this.getW         = (function(){ return params.w })();
  this.getH         = (function(){ return params.h })();
  this.getMouseover = (function(){ return params.mouseover })();
  this.getMouseout  = (function(){ return params.mouseout })();

  this.surface = new Surface({
    size: [this.getW, this.getW],

    properties:{
      backgroundColor: '#FA5C4F',
      boxShadow: "0 0 50px rgba(0,0,0,0.5)",
    }
  });

  this.translate = new StateModifier({
    origin: [.5,.5],
    transform: Transform.translate(this.getX,this.getY,this.getZ)
  })

  this.rotate = new StateModifier({
    transform: Transform.rotateY(-this.getAngle)
  })
}

var c = new Card({
    x: 0,
    y: 0,
    z: 0,
    w: 500,
    h: 500,
    angle: 0
})

mainContext.add(c.translate).add(c.rotate).add(c.surface);

Чтобы поверхности выглядела более живой, рандомно добавим вращение, как будто карточка раскачивается на ветру. Направление ветра инициализируем случайными значениями и меняется на противоположное при каждой итерации. Время анимации и таймаут также задаем случайно.

  this.wind = new StateModifier();
  this.windDirect = Math.random() > 0.5? -1: 1;

  (function wind(){
    self.windDirect = - self.windDirect;
    var duration = 1000 + Math.random() * 2000;
    self.wind.setTransform(Transform.rotateY(Math.random()*self.windDirect * Math.PI / 36), {duration: duration})
    setTimeout(function(){
      wind()
    } , duration + Math.random() * 5000);
  })();

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

var GaleryCard = function (number){
  this.prototype = Object.create(Card);

  var level = Math.round(number / 2);
  var lr = (number   % 2) == 0?-1:1;
  var x, y = 0, z = -level * 70, angle, w = 400, h = 400;

  if (level == 0){
    x = 0; y = 0; z = 0; angle = 0;w = 500; h =500;
  }
  else{
    x = (lr * level * 100) + (lr * 70);
    angle = Math.PI/6* lr;
  }

  return new Card({
    x: x,
    y: y,
    z: z,
    angle:angle,
    w: w,
    h: h
  });
}

var cards = [],
  cardsCount = 7;

for (var i = 0; i < cardsCount; i++){
  cards.push(new GaleryCard(i));
};

for (i in cards)
  mainContext.add(cards[i].translate).add(cards[i].wind).add(cards[i].rotate).add(cards[i].surface);

Если браузер развернут достаточно широко, то все выглядит достаточно красиво, однако что делать если размер экрана маленький? Карточки просто не влазят в экран. Все это происходит потому что размер задается в пикселах, чтобы решить эту проблему надо масштабировать размер карточек пропорционально размеру экрана. Сделаем масштабирование самым простым способом - пересчитав координаты пропорционально размеру экрана.

var calc = function(x){
  return window.innerWidth * x / 1300;
}

return new Card({
  x: calc(x),
  y: calc(y),
  z: calc(z),
  angle:angle,
  w: calc(w),
  h: calc(h),
});

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

Теперь карточки красиво раскачиваются на ветру. И мы добавим немного итеративности. Сделаем чтобы карточка выезжала при наведении на нее курсора.

Центральная карточка будет выезжать по оси Z. начнем с нее. Добавим обработчик mouseover и mouseout

  mouseover = function(data){
    var self = this;
    self.isMouseOver = true;
    setTimeout(function() {
      if (!self.isMouseOver) return;
      var sign = self.__getAngle < 0 ? -1 : 1;
      self.__translate.setTransform(
        Transform.translate(self.getX + calc(sign * 150), self.getY, self.getZ+calc(30)),
        {duration: 500, curve: Easing.inCubic }
      )
      self.rotate.setTransform(Transform.rotateZ(sign * Math.PI / 12), { duration: 500, curve: Easing.inCubic })
    }, 500);
  }

С анимацией при наведении курсора возникла проблема, так как анимация выполняется долго, то если быстро наводить и убирать курсор, то задания накладываются одно на другое и приложение достаточно долго отрисовывает анимацию, которая уже не актуальна. В CSS для 3d трансформации предусмотрен стиль webkit-transition-delay, который позволяет установить задержку перед отрисовкой графики. В famous я подобного не нашел (возможно плохо искал), поэтому сделал задержку в отрисовке на таймере.

Передаем обработчики в Card:

  return new Card({
    x: calc(x),
    y: calc(y),
    z: calc(z),
    angle:angle,
    w: calc(w),
    h: calc(h),
    mouseover: mouseover,
    mouseout: mouseout
    });

В констукторе класса Card привяжем обработчики к поверхности

  this.surface.on('mouseover', function(){self.getMouseover()});
  this.surface.on('mouseout', function(){self.getMouseout()});
  this.surface.on('touchmove', function(){self.getMouseover()});
  this.surface.on('touchend', function(){self.getMouseout()});

И в конце концов, оформим скрипт в виде модуля и добавим изображения.


Оставьте свой комментарий

comments powered by Disqus
Меню

Cult of digits 2014 Яндекс.Метрика