Разработка с Flask, ReactJS, gulp.js, bower, Browserify. Часть 1.

main-logo
image-4292

Давайте посмотрим на небольшую, но мощную JavaScript UI библиотеку ReactJS в действии. Это приложение работает на Python 3 и фремворке Flask, на back-end. Так же мы будем использовать на front-end: gulp.js (для выполнения задач), bower(front-end менеджер пакетов), и Browserify(пакетирование зависимостей JavaScript).

ReactJS

Реагировать библиотека, а не фреймворк. В отличие от клиентских MVC фреймворков таких как: Backbone, Ember, и AngularJS, React не делает никаких предположений о вашей технологии стека, так что вы можете легко интегрировать его в новый или унаследованного кода. Это часто используется, чтобы управлять конкретных областей UI приложение, а не всем пользовательским интерфейсом.

React заботится только о пользовательском интерфейсе, который определяется иерархией модульных views компонентов. Если вы знакомы с Anmgular эти компоненты аналогичны директивам. Компоненты используют синтаксис XML, именуемый JSX, который собирает в JavaScript.

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

Настройка проекта

Давайте начнем с того, что мы знаем: Flask.

Скачать шаблонный кода из репозитория, извлеките файлы, создавайте и активируйте virtualenv, и установите необходимые модули: pip install -r requirements.txt

Теперь давайте запустим приложение:

 $ sh run.sh 

React — первый раунд

Давайте посмотрим на простой компонент.

Компонент: переход от статичного к React

Мы добавим этот скрипт JSX в наш hello.html. Остановитесь на минутку, чтобы проверить его.

<script type="text/jsx">
  /*** @jsx React.DOM */
  var realPython = React.createClass({
    render: function() {
      return (
<h2>Greetings, from Real Python!</h2>
)
    }
  });
  React.render(
    React.createElement(realPython, null),
    document.getElementById('content')
  );
</script>

Что происходит?

  1. Мы создаем компонент с помощью вызова createClass(), и дали ему имя realPython. React.createClass() принимает один аргумент, объект.
  2. Внутри этого объекта мы добавили функцию render, которая декларативно обновляет DOM во время вызова.
  3. Затем идет возвращение значения Greetings, from Real Python!, в JSX, который представляет актуальный HTML элемент, который будет добавлен к DOM.
  4. В конце, React.render() создает экземпляр компонента realPython и вводит элемент в DOM с ID селектором content.

Трансформация

Что дальше? Мы должны «трансформировать», или скомпилировать, JSX в JavaScript. Это делается очень просто. Обновляем hello.html:

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="UTF-8">
    <title>Flask React</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- styles -->
  </head>
  <body>
<div class="container">
<h1>Flask React</h1>
<div id="content"></div>
    </div>
    <!-- scripts -->
    <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.12.0/react.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/react/0.12.0/JSXTransformer.js"></script>
    <script type="text/jsx">
      /*** @jsx React.DOM */
      var helloWorld = React.createClass({
        render: function() {
          return (
<h2>Greetings, from Real Python!</h2>
)
        }
      });
      React.render(
        React.createElement(helloWorld, null),
        document.getElementById('content')
      );
    </script>
  </body>
</html>

Здесь мы добавили скрипты react.js и JSXTransformer.js. Последний из них используется для «преобразования» синтаксис JSX в обычный JavaScript в браузере.

Обратите внимание, мы не добавили JQuery, так как это не требуется для React.

Вот и все. Запустите сервер Flask и проверьте результат в браузере по адресу: http://localhost:5000/hello

flask-react-hello-world
image-4293

Bower

Вместо того чтобы вручную скачивать JavaScript файлы или подключать их с CDN, давайте использовать Bower для лучшего управлениями этими зависимостями. Bower это мощный менеджер пакетов для фронтальных зависимостей, таких как: JQuery, Bootstrap, React, Angular, Backbone.

Убедитесь, что у вас уже установлен Node и npm. Если его нет то установите его.

Инициализация

Установим Bower с помощью npm:

$ npm install -g bower

npm это менеджер пакетов используется для управления модулями Node. В отличие от PyPi/pip, npm по умолчанию устанавливает зависимости локально. Флаг -g используется для переопределения, чтобы установить Bower глобально.

bower.json

Bower использует файл bower.json для определения зависимостей проекта, который похож на файл requirements.txt. Выполните следующую команду, чтобы в интерактивном режиме создать этот файл:

$ bower init

Просто нажимайте enter что бы установить значения по умолчанию. После этого файл bower.json должен выглядеть примерно так:

{
"name": "ultimate-flask-front-end",
"version": "0.0.1",
"authors": [
"Michael Herman <michael@realpython.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

npm

npm использует аналогичный bower.json файл, называется package.json, он определяет зависимости проекта. Вы также можете создать его в интерактивном режиме:

$ npm init

Так же установите значения по умолчанию:

{
"name": "ultimate-flask-front-end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

Теперь, давайте добавим файл bower в npm зависимости:

$ npm install --save-dev bower

Конфигурация

Наряду с файлом bower.json, мы можем определить параметры конфигурации Bower в файле .bowerrc. Создадим такой файл в корне проекта. Структура проекта должна теперь выглядеть следующим образом:

├── .bowerrc
├── .gitignore
├── bower.json
├── package.json
├── project
│   ├── app.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── scripts
│   └── templates
│       ├── hello.html
│       └── index.html
├── requirements.txt
└── run.sh

По умолчанию Bower установит пакеты в каталоге «bower_components» в корне проекта. Мы должны изменить это, так как Flask необходимо что бы файлы лежали в static. Таким образом, добавьте следующий JSON код в файл .bowerrc, так что бы Bower автоматически устанавливал файл в нужную директорию:

{
"directory": "./project/static/bower_components"
}

Инициализация

Мы должны установить следующие пакеты для этого проекта:

  • Bootstrap
  • jQuery
  • React

Это можно сделать двумя способами:

  1. Запустите bower install --save для каждого из пакетов (флаг —save добавляет зависимость (название и версию) в файле bower.json.).
  2. Обновите файл bower.json добавив названию и версию пакета, а затем запустить bower install, чтобы установить все зависимости от файла.

Давайте воспользуемся вторым способом.

{
"name": "ultimate-flask-front-end",
"version": "0.0.1",
"authors": [
"Michael Herman <michael@realpython.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"./project/static/bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "~3.3.2",
"jquery": "~2.1.3",
"react": "~0.12.2"
}
}

И запустим bower install

$ bower install
bower cached git://github.com/twbs/bootstrap.git#3.3.2
bower validate 3.3.2 against git://github.com/twbs/bootstrap.git#~3.3.2
bower cached git://github.com/jquery/jquery.git#2.1.3
bower validate 2.1.3 against git://github.com/jquery/jquery.git#~2.1.3
bower cached git://github.com/facebook/react-bower.git#0.12.2
bower validate 0.12.2 against git://github.com/facebook/react-bower.git#~0.12.2
bower install react#0.12.2
bower install jquery#2.1.3
bower install bootstrap#3.3.2

react#0.12.2 project/static/bower_components/react

jquery#2.1.3 project/static/bower_components/jquery

bootstrap#3.3.2 project/static/bower_components/bootstrap
└── jquery#2.1.3

Теперь вы можете проверить файлы в каталоге «project/static/bower_components».

Тест

Обновите hello.html:

<script src="{{ url_for('static', filename='bower_components/react/react.min.js') }}"></script>
<script src="{{ url_for('static', filename='bower_components/react/JSXTransformer.js') }}"></script>

Протестируйте приложение, чтобы убедиться, что оно все еще работает.

Простой блог на Flask

Создание блога кажется квинтэссенцией опыта при изучении нового веб-фреймворк. Я надеюсь, что этот пост даст вам инструменты для создания блога. В этой статье мы рассмотрим основы, чтобы получить функциональный сайт, но оставим много места для персонализации и улучшения. Фактический исходник блога поместится в 200 строчек Python кода.

Эта статья предназначено для начинающих разработчиков Python, среднего уровня, или опытных разработчиков, которые решили изучить Flask.

Спецификация

Особенности создаваемого блога:

  • Записи, отформатированные с использованием markdown
  • Записи поддерживают подсветку синтаксиса
  • Автоматическое видео/мультимедиа вставка с помощью OEmbed.
  • Очень красивый полнотекстовый поиск благодаря расширению SQLite FST.
  • Пагинация
  • Публикация записей

Вот примерный вид того, как блог будет выглядеть в конце.

Главная страница

p1425775025.68_800x800
image-4249

 

Страница записей

p1425775019.9_800x800
image-4250

 

Начинаем

Если вы хотите, чтобы пропустить статью и перейти непосредственно к коду, вы можете перейти по ссылке на GitHub.

Для начала, давайте создадим virtualenv и установим необходимые пакеты. Virtualenv, это практически стандартная библиотека, и она используется для создания изолированных, автономных сред Python, в которую вы можете установить свои пакеты. Посмотрите документацию по установке virtualenv.

Для нашего приложения мы должны установить следующие пакеты:

  • Flask, сам веб-фреймворк.
  • Peewee,  для хранения записей в базе данных и выполнения запросов.
  • pygments, подсветка синтаксиса с поддержкой огромного количества разных языков.
  • Markdown, форматирование для наших записей.
  • micawber, для преобразования адресов в обьекты. Например, если вы хотите встроить видео с YouTube, просто поместите URL на видео в свой пост и видео-плеер будет автоматически появляться на этом месте.
  • BeautifulSoup, требуется Микобером для разбора HTML.

 

$ virtualenv blog
New python executable in blog/bin/python2
Also creating executable in blog/bin/python
Installing setuptools, pip...done.
$ cd blog/
$ source bin/activate
(blog)$ pip install flask peewee pygments markdown micawber BeautifulSoup
...
Successfully installed flask peewee pygments markdown micawber BeautifulSoup Werkzeug Jinja2 itsdangerous markupsafe
Cleaning up...

Наш приложение будет находится в файле app.py. Мы также создадим несколько папок для статических файлов(стилей, JavaScript файлов) и папку для шаблонов.

(blog)$ mkdir app
(blog)$ cd app
(blog)$ touch app.py
(blog)$ mkdir {static,templates}

Настройка приложения Flask

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

# app.py
import datetime
import functools
import os
import re
import urllib
from flask import (Flask, abort, flash, Markup, redirect, render_template,
                   request, Response, session, url_for)
from markdown import markdown
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.extra import ExtraExtension
from micawber import bootstrap_basic, parse_html
from micawber.cache import Cache as OEmbedCache
from peewee import *
from playhouse.flask_utils import FlaskDB, get_object_or_404, object_list
from playhouse.sqlite_ext import *
ADMIN_PASSWORD = 'secret'
APP_DIR = os.path.dirname(os.path.realpath(__file__))
DATABASE = 'sqliteext:///%s' % os.path.join(APP_DIR, 'blog.db')
DEBUG = False
SECRET_KEY = 'shhh, secret!'  # Used by Flask to encrypt session cookie.
SITE_WIDTH = 800
app = Flask(__name__)
app.config.from_object(__name__)
flask_db = FlaskDB(app)
database = flask_db.database
oembed_providers = bootstrap_basic(OEmbedCache())

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

Определение моделей баз данных

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

У модели записей будут следующие столбцы:

  • title
  • slug: для понятного url.
  • content: содержание записи.
  • published:  флаг, указывающий публикуется ли запись.
  • timestamp: раз создается запись.
  • id: PeeWee автоматически создаст первичный ключ автоинкрементный для нас, так что мы не должны определять его явно.

Индекс поиска будут храниться с использованием модели класса FTSEntry:

  • entry_id: первичный ключ индексированной записи.
  • content: поиск контента для данной записи.

Добавим следующий код после конфигурации в наш app.py:

class Entry(flask_db.Model):
    title = CharField()
    slug = CharField(unique=True)
    content = TextField()
    published = BooleanField(index=True)
    timestamp = DateTimeField(default=datetime.datetime.now, index=True)
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = re.sub('[^\w]+', '-', self.title.lower())
        ret = super(Entry, self).save(*args, **kwargs)
        # Store search content.
        self.update_search_index()
        return ret
    def update_search_index(self):
        try:
            fts_entry = FTSEntry.get(FTSEntry.entry_id == self.id)
        except FTSEntry.DoesNotExist:
            fts_entry = FTSEntry(entry_id=self.id)
            force_insert = True
        else:
            force_insert = False
        fts_entry.content = '\n'.join((self.title, self.content))
        fts_entry.save(force_insert=force_insert)
class FTSEntry(FTSModel):
    entry_id = IntegerField(Entry)
    content = TextField()
    class Meta:
        database = database

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

Также обратите внимание, что мы создали несколько полей с индексом = True. Это говорит Peewee создать вторичный индекс по столбцам.

 

Код инициализации

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

@app.template_filter('clean_querystring')
def clean_querystring(request_args, *keys_to_remove, **new_values):
    querystring = dict((key, value) for key, value in request_args.items())
    for key in keys_to_remove:
        querystring.pop(key, None)
    querystring.update(new_values)
    return urllib.urlencode(querystring)
@app.errorhandler(404)
def not_found(exc):
    return Response('
&amp;amp;amp;lt;h3&amp;amp;amp;gt;Not found&amp;amp;amp;lt;/h3&amp;amp;amp;gt;
'), 404
def main():
    database.create_tables([Entry, FTSEntry], safe=True)
    app.run(debug=True)
if __name__ == '__main__':
    main()

Если вы хотите, вы можете попробовать запустить приложение сейчас. Но вы не сможем сделать какие-либо запросы, так как нет еще views, но база данных будет создана, и вы увидите следующий вывод:

$ cd blog  # switch to the blog virtualenv directory.
$ source bin/activate  # activate the virtualenv
(blog)$ cd app  # switch to the app subdirectory
(blog)$ python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with reloader

Добавим функции входа и выхода

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

def login_required(fn):
    @functools.wraps(fn)
    def inner(*args, **kwargs):
        if session.get('logged_in'):
            return fn(*args, **kwargs)
        return redirect(url_for('login', next=request.path))
    return inner
@app.route('/login/', methods=['GET', 'POST'])
def login():
    next_url = request.args.get('next') or request.form.get('next')
    if request.method == 'POST' and request.form.get('password'):
        password = request.form.get('password')
        if password == app.config['ADMIN_PASSWORD']:
            session['logged_in'] = True
            session.permanent = True  # Use cookie to store session.
            flash('You are now logged in.', 'success')
            return redirect(next_url or url_for('index'))
        else:
            flash('Incorrect password.', 'danger')
    return render_template('login.html', next_url=next_url)
@app.route('/logout/', methods=['GET', 'POST'])
def logout():
    if request.method == 'POST':
        session.clear()
        return redirect(url_for('login'))
    return render_template('logout.html')

Обратите внимание, что вьюхи входа и выхода делают разные вещи в зависимости от того, был ли запрос GET или POST. При переходе на страницу /login/, вы увидите шаблон с полем ввода. Когда вы отправляете форму, вьюха будет проверять пароль, и перенаправить на главную или отобразить сообщение об ошибке.

Реализация views

Теперь, когда мы создали фундамент нашего сайта, мы можем начать работать над тем, что на самом деле будет использоваться для отображения и управления записей в блоге. Благодаря некоторым из помощников в модуле flask_utils Playhouse, код будет минимальным.

Главная, поиск, черновики

Давайте начнем с главной страницы. Это будет страница с записями, с сортировкой по времени, и будут отображаться последние 20 записей. Мы будем использовать object_list из flask_utils, который принимает запрос и возвращает запрошенную страницу объектов. Кроме того, индекс страницы позволит пользователям осуществлять поиск.

Добавим следующий код после кода аутентификации:

@app.route('/')
def index():
    search_query = request.args.get('q')
    if search_query:
        query = Entry.search(search_query)
    else:
        query = Entry.public().order_by(Entry.timestamp.desc())
    return object_list('index.html', query, search=search_query)

Если поисковый запрос присутствует, как указано в GET аргумента q, будем вызывать метод Entry.search(). Этот метод будет использовать SQLite полнотекстовый поисковый индекс для запроса соответствующих записей. Полнотекстовый поиск SQLite поддерживает логические запросы, цитирование, и многое другое.

Вы можете заметить, что мы также вызываем Entry.public(),  если нет поискового запроса. Этот метод вернет только опубликованные записи.

Для реализации этого, добавим следующие методы в наш класс:

@classmethod
def public(cls):
    return Entry.select().where(Entry.published == True)
@classmethod
def search(cls, query):
    words = [word.strip() for word in query.split() if word.strip()]
    if not words:
        # Return empty query.
        return Entry.select().where(Entry.id == 0)
    else:
        search = ' '.join(words)
    return (FTSEntry
            .select(
                FTSEntry,
                Entry,
                FTSEntry.rank().alias('score'))
            .join(Entry, on=(FTSEntry.entry_id == Entry.id).alias('entry'))
            .where(
                (Entry.published == True) &amp;amp;
                (FTSEntry.match(search)))
            .order_by(SQL('score').desc()))

Давайте кратко разберемся в методе search. Здесь мы запрашиваем таблицу FTSEntry, в которой хранится поисковые индексы наших записей. Полнотекстовый поиск SQLite внедряет оператор MATCH, который мы будем использовать, чтобы соответствовать индексированный контент от поискового запроса. Мы также присоединяемся к таблице записей таким образом, что мы вернем только опубликованные записи.

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

@classmethod
def drafts(cls):
    return Entry.select().where(Entry.published == False)
@app.route('/drafts/')
@login_required
def drafts():
    query = Entry.drafts().order_by(Entry.timestamp.desc())
    return object_list('index.html', query)

Детальная страница записей

Мы будем использовать дружественные URL. Вы, возможно, помните, что мы перегрузили метод Entry.save() для заполнения поля slug с URL. Добавим следующий код в наше приложение:

@app.route('/<slug>/')
def detail(slug):
    if session.get('logged_in'):
        query = Entry.select()
    else:
        query = Entry.public()
    entry = get_object_or_404(query, Entry.slug == slug)
    return render_template('detail.html', entry=entry)

Get_object_or_404 определен в модуле Playhouse flask_utils и, если объект, соответствующих запросу не найдено, возвращает ответ 404.

Отображение содержание записей

Для того, чтобы преобразовать тексты записей в формате HTML, мы добавим дополнительное свойство к нашему классу. Это свойство будет включить содержание записи в HTML и конвертировать медиа ссылки в встроенных объекты (т.е. URL YouTube становится видеоплеер).

Добавим следующее свойство модели:

@property
def html_content(self):
    hilite = CodeHiliteExtension(linenums=False, css_class='highlight')
    extras = ExtraExtension()
    markdown_content = markdown(self.content, extensions=[hilite, extras])
    oembed_content = parse_html(
        markdown_content,
        oembed_providers,
        urlize_all=True,
        maxwidth=app.config['SITE_WIDTH'])
    return Markup(oembed_content)

Объект Markup говорит Flask что мы доверяем содержание HTML, поэтому он не будет обрезать его при выводе.

Создание и редактирование записей

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

@app.route('/create/', methods=['GET', 'POST'])
@login_required
def create():
    if request.method == 'POST':
        if request.form.get('title') and request.form.get('content'):
            entry = Entry.create(
                title=request.form['title'],
                content=request.form['title'],
                published=request.form.get('published') or False)
            flash('Entry created successfully.', 'success')
            if entry.published:
                return redirect(url_for('detail', slug=entry.slug))
            else:
                return redirect(url_for('edit', slug=entry.slug))
        else:
            flash('Title and Content are required.', 'danger')
    return render_template('create.html')
@app.route('/<slug>/edit/', methods=['GET', 'POST'])
@login_required
def edit(slug):
    entry = get_object_or_404(Entry, Entry.slug == slug)
    if request.method == 'POST':
        if request.form.get('title') and request.form.get('content'):
            entry.title = request.form['title']
            entry.content = request.form['content']
            entry.published = request.form.get('published') or False
            entry.save()
            flash('Entry saved successfully.', 'success')
            if entry.published:
                return redirect(url_for('detail', slug=entry.slug))
            else:
                return redirect(url_for('edit', slug=entry.slug))
        else:
            flash('Title and Content are required.', 'danger')
    return render_template('edit.html', entry=entry)

Далее осталось добавить только шаблоны у статический файлы. Их вы можете скачать с GitHux, ссылка в начале статьи.

Зум слайдер

Простой слайдер контента с функцией зума в каждом слайде.

ZoomSlider
image-3065

Демо

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

Мы используем CSS переходы и dymanic.js для перемещения элементов слайд. Dymanic.js Майкла Виллара является библиотекой JavaScript для создания физики на основе анимации.

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

HTML

<!-- Main container -->
<div class="container">
	<!-- Blueprint header -->
	<header class="bp-header cf">
		<!-- Page title etc. -->
	</header>
	<!-- Grid -->
	<section class="slider">
		<div class="slide slide--current" data-content="content-1">
			<div class="slide__mover">
				<div class="zoomer flex-center">
					<img class="zoomer__image" src="images/iphone.png" alt="iPhone" />
					<div class="preview">
						<img src="images/iphone-content-preview.png" alt="iPhone app preview" />
						<div class="zoomer__area zoomer__area--size-2"></div>
					</div>
				</div>
			</div>
			<h2 class="slide__title"><span>The Classy</span> iPhone 6</h2>
		</div>
		<div class="slide" data-content="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<nav class="slider__nav">
			<button class="button button--nav-prev">
				<i class="icon icon--arrow-left"></i>
				<span class="text-hidden">Previous product</span>
			</button>
			<button class="button button--zoom">
				<i class="icon icon--zoom"></i>
				<span class="text-hidden">View details</span>
			</button>
			<button class="button button--nav-next">
				<i class="icon icon--arrow-right"></i>
				<span class="text-hidden">Next product</span>
			</button>
		</nav>
	</section>
	<!-- /slider-->
	<!-- content -->
	<section class="content">
		<div class="content__item" id="content-1">
			<img class="content__item-img rounded-right" src="images/iphone-content.png" alt="Apple Watch Content" />
			<div class="content__item-inner">
				<h2>The iPhone 6</h2>
				<h3>Incredible performance for powerful apps</h3>
				<p>...</p>
			</div>
		</div>
		<div class="content__item" id="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<button class="button button--close">
			<i class="icon icon--circle-cross"></i>
			<span class="text-hidden">Close content</span>
		</button>
	</section>
	<!-- /content -->
</div>
<script src="js/classie.js"></script>
<script src="js/dynamics.min.js"></script>
<script src="js/main.js"></script>

CSS

/* Helper classes */
html,
body {
	overflow: hidden;
	height: 100%;
}
.container {
	position: relative;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100%;
	-webkit-overflow-scrolling: touch;
}
.noscroll .container {
	overflow-y: hidden;
}
.slider {
	position: relative;
	z-index: 200;
	width: 100%;
	margin: 0 auto;
	padding: 0 0 7em;
	text-align: center;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	-webkit-touch-callout: none;
	-khtml-user-select: none;
}
.slide {
	position: absolute;
	top: 0;
	visibility: hidden;
	width: 100%;
	opacity: 0;
}
.slide--current {
	position: relative;
	z-index: 100;
	visibility: visible;
	opacity: 1;
}
.slide__mover {
	position: relative;
	z-index: 100;
}
.slide__title {
	font-size: 1.75em;
	font-weight: normal;
	margin: 0 auto;
	padding: 1em 0 0 0;
}
.slide__title span {
	font-size: 55%;
	font-weight: bold;
	display: block;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: #35303d;
}
.slider__nav {
	position: absolute;
	bottom: 2em;
	width: 100%;
	text-align: center;
}
.button {
	font-size: 1.31em;
	position: relative;
	display: inline-block;
	overflow: hidden;
	margin: 0 25px;
	padding: 0;
	cursor: pointer;
	color: #5c5edc;
	border: none;
	background: none;
}
.button:focus {
	outline: none;
}
.button:hover {
	color: #fff;
}
.text-hidden {
	position: absolute;
	top: 200%;
}
.button--close {
	font-size: 1.55em;
	position: absolute;
	top: 30px;
	right: 30px;
	margin: 0;
	opacity: 0;
	color: #50505a;
	-webkit-transition: opacity 0.3s;
	transition: opacity 0.3s;
}
.content--open .button--close {
	opacity: 1;
}
/* Zoomer */
.zoomer {
	position: relative;
	height: 360px; /* this is needed for IE10 so that vertical flexbox centering works */
}
.flex-center {
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
	-webkit-justify-content: center;
	-ms-flex-pack: center;
	justify-content: center;
}
.zoomer__image {
	display: block;
	margin: 0;
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}
.zoomer__area,
.preview {
	position: absolute;
	top: 50%;
	left: 50%;
	-webkit-transform: translate3d(-50%,-50%,0);
	transform: translate3d(-50%,-50%,0);
}
.zoomer__area:focus {
	outline: none;
}
.zoomer__area--size-1 {
	/* Apple Watch */
	width: 96px;
	height: 118px;
}
.zoomer__area--size-2 {
	/* iPhone */
	width: 112px;
	height: 198px;
}
.zoomer__area--size-3 {
	/* MacBook */
	width: 315px;
	height: 200px;
}
.zoomer__area--size-4 {
	/* iPad */
	width: 150px;
	height: 200px;
}
.zoomer__area--size-5 {
	/* iMac */
	width: 315px;
	height: 189px;
}
.preview {
	overflow: hidden;
	background: #18191b;
}
.preview img {
	display: block;
	border-radius: inherit;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}
.zoomer--active .preview img {
	-webkit-transform: translate3d(100%,0,0);
	transform: translate3d(100%,0,0);
}
.rounded {
	border-radius: 15px;
}
.rounded-right {
	border-radius: 0 15px 15px 0;
}
.preview__content {
	position: absolute;
	top: 0;
	left: 100%;
	width: 100%;
	height: 100%;
	border-radius: inherit;
}
/* Content */
.content {
	position: fixed;
	z-index: 1000;
	top: 0;
	left: -100%;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100vh;
	background: #18191b;
	-webkit-overflow-scrolling: touch;
}
.content--open {
	left: 0;
}
.content__item {
	position: absolute;
	top: 0;
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	overflow: hidden;
	height: 0;
	min-height: 100%;
	margin: 0 auto;
	padding: 2em 0;
	pointer-events: none;
	opacity: 0;
	color: #fff;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
}
.content__item--current {
	pointer-events: auto;
	opacity: 1;
}
.content__item--reset {
	height: auto;
}
.content h2 {
	font-size: 3.5em;
	font-weight: normal;
	margin: 0;
}
.content h3 {
	font-size: 1.95em;
	font-weight: normal;
	margin: 0.25em 0 0.5em;
	color: #685884;
}
.content p {
	font-size: 1.25em;
	line-height: 1.5;
}
.content__item-img {
	display: block;
	max-width: 40vw;
	max-height: 80vh;
	-webkit-transform: translate3d(-120%,0,0);
	transform: translate3d(-120%,0,0);
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}
.content__item--current .content__item-img {
	-webkit-transform: translate3d(-10px,0,0);
	transform: translate3d(-10px,0,0);
}
.content__item-inner {
	padding: 0 10vw 0;
	opacity: 0;
	-webkit-transform: translate3d(0,50px,0);
	transform: translate3d(0,50px,0);
}
.content__item--current .content__item-inner {
	opacity: 1;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}
/**************************/
/* All synced transitions */
/**************************/
.zoomer {
	-webkit-transition: -webkit-transform 0.5s;
	transition: transform 0.5s;
	-webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1);
	transition-timing-function: cubic-bezier(0.7,0,0.3,1);
}
.zoomer.zoomer--notrans {
	-webkit-transition: none;
	transition: none;
}
.zoomer__image {
	-webkit-transition: opacity 0.3s 0.3s;
	transition: opacity 0.3s 0.3s;
}
.zoomer--active .zoomer__image {
	opacity: 0;
	-webkit-transition-delay: 0s;
	transition-delay: 0s;
}
.preview img {
	-webkit-transition: -webkit-transform 0.6s 0.3s;
	transition: transform 0.6s 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
}
.zoomer--active .preview img {
	-webkit-transition: -webkit-transform 0.3s;
	transition: transform 0.3s;
}
.content {
	-webkit-transition: left 0s;
	transition: left 0s;
}
.content__item {
	-webkit-transition: opacity 0s;
	transition: opacity 0s;
}
.content,
.content__item {
	/* delay for content to disappear and zoomer to start transitioning back to 0 */
	-webkit-transition-delay: 0.3s;
	transition-delay: 0.3s;
}
.content--open,
.content__item--current {
	-webkit-transition: none;
	transition: none;
}
.content__item-img {
	-webkit-transition: -webkit-transform 0.4s;
	transition: transform 0.4s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1);
	transition-timing-function: cubic-bezier(0.7,1,0.8,1);
}
.content__item--current .content__item-img {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	-webkit-transition-duration: 1s;
	transition-duration: 1s;
}
.content__item-inner {
	-webkit-transition: -webkit-transform 0.6s, opacity 0.3s;
	transition: transform 0.6s, opacity 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
	transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
}
.content__item--current .content__item-inner {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	-webkit-transition-duration: 1.7s;
	transition-duration: 1.7s;
}
/* Media Queries */
@media screen and (max-width: 50em) {
	.content__item {
		display: block;
	}
	.content__item-img {
		max-width: calc(100% - 80px);
		max-height: 70vh;
	}
	.content h2 {
		font-size: 3em;
	}
	.content__item-inner {
		font-size: 82%;
		padding: 4em 3em 2em;
	}
}

JavaScript

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2015, Codrops
 * http://www.codrops.com
 */
;(function(window) {
	'use strict';
	var bodyEl = document.body,
		docElem = window.document.documentElement,
		support = { transitions: Modernizr.csstransitions },
		// transition end event name
		transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' },
		transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
		onEndTransition = function( el, callback ) {
			var onEndCallbackFn = function( ev ) {
				if( support.transitions ) {
					if( ev.target != this ) return;
					this.removeEventListener( transEndEventName, onEndCallbackFn );
				}
				if( callback && typeof callback === 'function' ) { callback.call(this); }
			};
			if( support.transitions ) {
				el.addEventListener( transEndEventName, onEndCallbackFn );
			}
			else {
				onEndCallbackFn();
			}
		},
		// window sizes
		win = {width: window.innerWidth, height: window.innerHeight},
		// some helper vars to disallow scrolling
		lockScroll = false, xscroll, yscroll,
		scrollContainer = document.querySelector('.container'),
		// the main slider and its items
		sliderEl = document.querySelector('.slider'),
		items = [].slice.call(sliderEl.querySelectorAll('.slide')),
		// total number of items
		itemsTotal = items.length,
		// navigation controls/arrows
		navRightCtrl = sliderEl.querySelector('.button--nav-next'),
		navLeftCtrl = sliderEl.querySelector('.button--nav-prev'),
		zoomCtrl = sliderEl.querySelector('.button--zoom'),
		// the main content element
		contentEl = document.querySelector('.content'),
		// close content control
		closeContentCtrl = contentEl.querySelector('button.button--close'),
		// index of current item
		current = 0,
		// check if an item is "open"
		isOpen = false,
		isFirefox = typeof InstallTrigger !== 'undefined',
		// scale body when zooming into the items, if not Firefox (the performance in Firefox is not very good)
		bodyScale = isFirefox ? false : 3;
	// some helper functions:
	function scrollX() { return window.pageXOffset || docElem.scrollLeft; }
	function scrollY() { return window.pageYOffset || docElem.scrollTop; }
	// from http://www.sberry.me/articles/javascript-event-throttling-debouncing
	function throttle(fn, delay) {
		var allowSample = true;
		return function(e) {
			if (allowSample) {
				allowSample = false;
				setTimeout(function() { allowSample = true; }, delay);
				fn(e);
			}
		};
	}
	function init() {
		initEvents();
	}
	// event binding
	function initEvents() {
		// open items
		zoomCtrl.addEventListener('click', function() {
			openItem(items[current]);
		});
		// close content
		closeContentCtrl.addEventListener('click', closeContent);
		// navigation
		navRightCtrl.addEventListener('click', function() { navigate('right'); });
		navLeftCtrl.addEventListener('click', function() { navigate('left'); });
		// window resize
		window.addEventListener('resize', throttle(function(ev) {
			// reset window sizes
			win = {width: window.innerWidth, height: window.innerHeight};
			// reset transforms for the items (slider items)
			items.forEach(function(item, pos) {
				if( pos === current ) return;
				var el = item.querySelector('.slide__mover');
				dynamics.css(el, { translateX: el.offsetWidth });
			});
		}, 10));
		// keyboard navigation events
		document.addEventListener( 'keydown', function( ev ) {
			if( isOpen ) return;
			var keyCode = ev.keyCode || ev.which;
			switch (keyCode) {
				case 37:
					navigate('left');
					break;
				case 39:
					navigate('right');
					break;
			}
		} );
	}
	// opens one item
	function openItem(item) {
		if( isOpen ) return;
		isOpen = true;
		// the element that will be transformed
		var zoomer = item.querySelector('.zoomer');
		// slide screen preview
		classie.add(zoomer, 'zoomer--active');
		// disallow scroll
		scrollContainer.addEventListener('scroll', noscroll);
		// apply transforms
		applyTransforms(zoomer);
		// also scale the body so it looks the camera moves to the item.
		if( bodyScale ) {
			dynamics.animate(bodyEl, { scale: bodyScale }, { type: dynamics.easeInOut, duration: 500 });
		}
		// after the transition is finished:
		onEndTransition(zoomer, function() {
			// reset body transform
			if( bodyScale ) {
				dynamics.stop(bodyEl);
				dynamics.css(bodyEl, { scale: 1 });
				// fix for safari (allowing fixed children to keep position)
				bodyEl.style.WebkitTransform = 'none';
				bodyEl.style.transform = 'none';
			}
			// no scrolling
			classie.add(bodyEl, 'noscroll');
			classie.add(contentEl, 'content--open');
			var contentItem = document.getElementById(item.getAttribute('data-content'))
			classie.add(contentItem, 'content__item--current');
			classie.add(contentItem, 'content__item--reset');
			// reset zoomer transform - back to its original position/transform without a transition
			classie.add(zoomer, 'zoomer--notrans');
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
		});
	}
	// closes the item/content
	function closeContent() {
		var contentItem = contentEl.querySelector('.content__item--current'),
			zoomer = items[current].querySelector('.zoomer');
		classie.remove(contentEl, 'content--open');
		classie.remove(contentItem, 'content__item--current');
		classie.remove(bodyEl, 'noscroll');
		if( bodyScale ) {
			// reset fix for safari (allowing fixed children to keep position)
			bodyEl.style.WebkitTransform = '';
			bodyEl.style.transform = '';
		}
		/* fix for safari flickering */
		var nobodyscale = true;
		applyTransforms(zoomer, nobodyscale);
		/* fix for safari flickering */
		// wait for the inner content to finish the transition
		onEndTransition(contentItem, function(ev) {
			classie.remove(this, 'content__item--reset');
			// reset scrolling permission
			lockScroll = false;
			scrollContainer.removeEventListener('scroll', noscroll);
			/* fix for safari flickering */
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			/* fix for safari flickering */
			// scale up - behind the scenes - the item again (without transition)
			applyTransforms(zoomer);
			// animate/scale down the item
			setTimeout(function() {
				classie.remove(zoomer, 'zoomer--notrans');
				classie.remove(zoomer, 'zoomer--active');
				zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
				zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			}, 25);
			if( bodyScale ) {
				dynamics.css(bodyEl, { scale: bodyScale });
				dynamics.animate(bodyEl, { scale: 1 }, {
					type: dynamics.easeInOut,
					duration: 500
				});
			}
			isOpen = false;
		});
	}
	// applies the necessary transform value to scale the item up
	function applyTransforms(el, nobodyscale) {
		// zoomer area and scale value
		var zoomerArea = el.querySelector('.zoomer__area'),
			zoomerAreaSize = {width: zoomerArea.offsetWidth, height: zoomerArea.offsetHeight},
			zoomerOffset = zoomerArea.getBoundingClientRect(),
			scaleVal = zoomerAreaSize.width/zoomerAreaSize.height < win.width/win.height ? win.width/zoomerAreaSize.width : win.height/zoomerAreaSize.height;
		if( bodyScale && !nobodyscale ) {
			scaleVal /= bodyScale;
		}
		// apply transform
		el.style.WebkitTransform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
		el.style.transform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
	}
	// navigate the slider
	function navigate(dir) {
		var itemCurrent = items[current],
			currentEl = itemCurrent.querySelector('.slide__mover'),
			currentTitleEl = itemCurrent.querySelector('.slide__title');
		// update new current value
		if( dir === 'right' ) {
			current = current < itemsTotal-1 ? current + 1 : 0;
		}
		else {
			current = current > 0 ? current - 1 : itemsTotal-1;
		}
		var itemNext = items[current],
			nextEl = itemNext.querySelector('.slide__mover'),
			nextTitleEl = itemNext.querySelector('.slide__title');
		// animate the current element out
		dynamics.animate(currentEl, { opacity: 0, translateX: dir === 'right' ? -1*currentEl.offsetWidth/2 : currentEl.offsetWidth/2, rotateZ: dir === 'right' ? -10 : 10 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				dynamics.css(itemCurrent, { opacity: 0, visibility: 'hidden' });
			}
		});
		// animate the current title out
		dynamics.animate(currentTitleEl, { translateX: dir === 'right' ? -250 : 250, opacity: 0 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 450
		});
		// set the right properties for the next element to come in
		dynamics.css(itemNext, { opacity: 1, visibility: 'visible' });
		dynamics.css(nextEl, { opacity: 0, translateX: dir === 'right' ? nextEl.offsetWidth/2 : -1*nextEl.offsetWidth/2, rotateZ: dir === 'right' ? 10 : -10 });
		// animate the next element in
		dynamics.animate(nextEl, { opacity: 1, translateX: 0 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				items.forEach(function(item) { classie.remove(item, 'slide--current'); });
				classie.add(itemNext, 'slide--current');
			}
		});
		// set the right properties for the next title to come in
		dynamics.css(nextTitleEl, { translateX: dir === 'right' ? 250 : -250, opacity: 0 });
		// animate the next title in
		dynamics.animate(nextTitleEl, { translateX: 0, opacity: 1 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 650
		});
	}
	// disallow scrolling (on the scrollContainer)
	function noscroll() {
		if(!lockScroll) {
			lockScroll = true;
			xscroll = scrollContainer.scrollLeft;
			yscroll = scrollContainer.scrollTop;
		}
		scrollContainer.scrollTop = yscroll;
		scrollContainer.scrollLeft = xscroll;
	}
	init();
})(window);

Демо

Легкая автоматизация задач с Gulp.js

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

gulp-808x500[1]
image-978

Gulp предпочитает код по конфигурации. Будучи, что ваши задачи записываются в коде, Gulp дает вам инструменты для создания задач, которые соответствуют вашим потребностям.

Установка

Gulp легко установить и начать работать.

  1. Установка Gulp глобально
  2. Установка Gulp в devDependencies
  3. Создание gulpfile.js

Первый шаг, чтобы установить Gulp глобально.

$ npm install --global gulp

После этого, вам нужно добавить Gulp в devDependencies на любом из ваших проектов где вы хотите использовать его. Убедитесь, что у вас есть package.json. Если у вас есть package.json, давайте установим Gulp в devDependencies

$ npm install --save-dev gulp

И наконец, вы должны создать gulpfile.js в корне проекта, который содержит ваши задачи. В качестве промежуточного шага мы добавим Gulp Utilites модуль, у нас есть исполняемые задачи, которые явно показывает, что она выполнена.

$ npm install --save-dev gulp-util

В файле gulpfile.js, который вы только что создали, мы сделаем простой gulpfile, который только регистрирует, что Gulp работает.

/* File: gulpfile.js */
// grab our gulp packages
var gulp  = require('gulp'),
    gutil = require('gulp-util');
// create a default task and just log a message
gulp.task('default', function() {
  return gutil.log('Gulp is running!')
});

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

> gulp
[12:32:08] Using gulpfile ~/Projects/gulp-scotch-io/gulpfile.js
[12:32:08] Starting 'default'...
[12:32:08] Gulp is running!
[12:32:08] Finished 'default' after 1 ms

Обзор

Структура каталога для этого урока

Мы определили следующую структуру нашего проекта. Вы можете создать пустые файлы на данный момент.

public/
  |  assets/
  |  |  stylesheets/
  |  |  |  style.css
  |  |  javascript/
  |  |  |  vendor/
  |  |  |  |  jquery.min.js
  |  |  |  bundle.js
source/
  |  javascript/
  |  |  courage.js
  |  |  wisdom.js
  |  |  power.js
  |  scss/
  |  |  styles.scss
  |  |  grid.scss
gulpfile.js
packages.json

В source мы будем делать нашу работу. assets/style.css создает Gulp, когда мы обрабатываем и объединить наши Sass файлы в source/scss. Файл bundle.js создает так же Gulp, когда мы минимизируем и объединяем все наши JS файлы.

Краткий обзор Gulp

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

Gulp API является очень простым, содержащий 4 функции верхнего уровня.

  • gulp.task
  • gulp.src
  • gulp.dest
  • gulp.watch

gulp.task определяет ваши задачи. Его аргументы name, deps and fn.

Где name строка, deps является массив имен задача, и fn является функция, которая выполняет вашу задачу. deps не является обязательным, по этому gulp.task имеет две формы:

gulp.task('mytask', function() {
  //do stuff
});
gulp.task('dependenttask', ['mytask'], function() {
  //do stuff after 'mytask' is done.
});

gulp.src указывает на файлы, которые мы хотим использовать. Это глобальные параметры и является необязательной опцией. Он использует .pipe для построения цепочки из его вывода в других плагинов.

gulp.dest указывает на папку вывода куда мы хотим записать файлы.

gulp.src и gulp.dest простой пример для копирования файлов выглядит следующим образом:

gulp.task('copyHtml', function() {
  // copy any html files in source/ to public/
  gulp.src('source/*.html').pipe(gulp.dest('public'));
});

gulp.watch как gulp.task имеет две основные формы. Оба возвращает EventEmitter, который выделяют change событий. Первый из которых занимает глобально, необязательный параметр, и массив задач его параметров.

gulp.watch('source/javascript/**/*.js', ['jshint']);

Проще говоря, когда любой из файлов, глобально изменяется, выполняется задача. В приведенном выше примере, когда какие-либо файлы в source/javascript с расширением .js изменятся, то jshint задача будет выполнена в отношении тех файлов.

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

Для получения дополнительной информации обратитесь к API.

Некоторые полезные задачи.

Мы начнем с простых задач.

Jshint  на сохранение

Наша первая задача будет с нашим JavaScript (проверить на наличие ошибок), используя jshint и мы также настроить его для выполнения задания каждый раз, когда мы сохраним файл.

Для начала нам нужен пакет gulp-jshint, установим его с помощью npm. Мы также должны настроить jshint вывод в удобном формате и цветом оформлении.

$ npm install --save-dev gulp-jshint jshint-stylish

Теперь мы добавим задачу в наш gulpfile.

/* File: gulpfile.js */
// grab our packages
var gulp   = require('gulp'),
    jshint = require('gulp-jshint');
// define the default task and add the watch task to it
gulp.task('default', ['watch']);
// configure the jshint task
gulp.task('jshint', function() {
  return gulp.src('source/javascript/**/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});
// configure which files to watch and what tasks to use on file changes
gulp.task('watch', function() {
  gulp.watch('source/javascript/**/*.js', ['jshint']);
});

Мы переписали нашу задачу по умолчанию, чтобы следить за зависимостями задачи. Это означает, что для запуска

$ gulp

будем следить за выполнением задачи.

Теперь давайте посмотрим на jshint задачи. Его источники любые .js файлы, которые существуют в source/javascript или в подкаталогах. Так файл source/javascript/carousel/main.js будет определена для задачи так же.

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

Мы можем выполнить эту задачу, выполнив:

$ gulp jshint

Хорошо, теперь о задаче watch. Это просто на самом деле, если обнаруживается изменение в любом из наших файлов JavaScript, то запускается задача jshint.

Sass компиляция с libsass

Sass расширяет CSS и дает поддержку переменных, вложенных правил, mixins, встроенных импорта, и многое другое.

Для Sass компиляции мы будем использовать gulp-sass

/* file: gulpfile.js */
var gulp   = require('gulp'),
    jshint = require('gulp-jshint'),
    sass   = require('gulp-sass');
/* jshint task would be here */
gulp.task('build-css', function() {
  return gulp.src('source/scss/**/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('public/assets/stylesheets'));
});
/* updated watch task to include sass */
gulp.task('watch', function() {
  gulp.watch('source/javascript/**/*.js', ['jshint']);
  gulp.watch('source/scss/**/*.scss', ['build-css']);
});

Мы можем также добавить sourcemaps, используя gulp-sourcemaps.

/* file: gulpfile.js */
var gulp       = require('gulp'),
    jshint     = require('gulp-jshint'),
    sass       = require('gulp-sass'),
    sourcemaps = require('gulp-sourcemaps');
gulp.task('build-css', function() {
  return gulp.src('source/scss/**/*.scss')
    .pipe(sourcemaps.init())  // Process the original sources
      .pipe(sass())
    .pipe(sourcemaps.write()) // Add the map to modified source.
    .pipe(gulp.dest('public/assets/stylesheets'));
});

Javascript минификация

При работе с большим количеством JavaScript, вам как правило, необходимо собрать файлы вместе. Общего назначения плагин gulp-concat позволяет выполнить это легко.

Мы также можем пойти дальше и запустить его через минификацию и получить файл гораздо меньшего размера.

gulp.task('build-js', function() {
  return gulp.src('source/javascript/**/*.js')
    .pipe(sourcemaps.init())
      .pipe(concat('bundle.js'))
      //only uglify if gulp is ran with '--type production'
      .pipe(gutil.env.type === 'production' ? uglify() : gutil.noop())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('public/assets/javascript'));
});

Вывод

Мы рассмотрели только самый минимум Gulp. С ним легко соединить файлы JavaScript. Gulp дает вам инструменты, чтобы сделать то что вы хотите быстро и легко.

Vim подборка материала для изучения

vim-logo[1]
image-967

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

Если вы всегда использовали графический текстовый редактор такие как Блокнот или TextEdit, вам покажется Vim запутанным, но потратив некоторое время на изучение вы уже не захотите возвращаться к своим старым редакторам.

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

  1. OpenVim — Интерактивный учебник для изучения основ Vim. Перейдите на страницу практике что бы проверить свои навыки в Vim.
  2. Vim Adventures  — Онлайн игра-головоломка для обучения и запоминания команды Vim. Вы мигающий курсор, ориентируйтесь в лабиринте с помощью клавиатуры.
  3. Vim Genius — Эта игра, которая научит вас основам Vim.
  4. Learn to Love Vim — Видеурок от Linux Voice,  более наглядно показаны основы Vim.
  5. Vim Basics — Дерек Уайатт выпустил много видеоуроков о Vim. Для тех кто предпочитает наглядное обучение.
  6. Learning Vim — Виедо с Майк Коутермаш — вставая и работает.
  7. Vim – Precision Editing — Дрю Нил из Vimcasts.org проведет вас через Vim и как текстовый редактор, оптимизированный для Mouseless операций.
  8. Practical Vim — Единственная книга, которая когда-нибудь вам понадобится для освоения Vim.
  9.  Vim Tutorial — Официальная документация Vim включает в себя учебник, который вы также можете получить доступ в программе Vim через команду :vimtutor.
  10. Vim Cheat Sheet — Распечатайте это, потому что вы будете нуждаться в этом позже.
  11. A Byte of Vim — Бесплатно PDF книга, которая вам поможет в изучении редактора Vim.
  12. Vim 101 — Коллекция небольших статей, которые охватывают различные аспекты редактирования с Vim.

Если вы проводите много времени при вводе текста, изучение Vim будет полностью стоить усилий потраченного на него.

11 классных шрифтов иконок для ваших проектов

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

  • масштабируемость
  • стилизация с помощью CSS (изменить размер, цвет и т.д.)
  • добавление границы и теней с помощью CSS
  • нет http запросов для изображений иконок

Это очень важные и ключевые преимущества.

Как использовать иконки шрифтов

Все, что вам нужно сделать, это загрузить CSS файл, и все уже готово!

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

<!-- подключаем css файл -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<!-- используем шрифт иконок с помощью классов -->
<i class="ti-fire"></i>
<span class="ti-user"></span>

Давайте посмотрим лучшие шрифты, которые вы можете использовать для ваших проектов.

FontAwesome

fontawesoek-905x500[1]
image-934

Themify Icons

themifyicons-905x500[1]
image-935

Foundation Icons

foundation-iconfonts-905x500[1]
image-936

Ionicons

ionicons-905x500[1]
image-937

Octicons

octicons-905x500[1]
image-938

Linecons

linecons-905x500[1]
image-939

Open Iconic

openiconic-905x500[1]
image-940

Entypo

entypo-905x500[1]
image-941

JustVector

justvectoricons-905x500[1]
image-942

SVG Icons

svgfonts-905x500[1]
image-943

Payment Icons

paymentfonticons-905x500[1]
image-944

Вывод.

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

Небольшое руководство по React

react-javascript-library-building-user-interfaces[1]
image-756

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

Основная концепция

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

Элементы React  являются объектами JavaScript, которые представляют HTML элементы. Они не существуют в браузере. Они представляют собой элементы браузера, такие как h1, div или section.

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

JSX представляет собой метод для создания React элементов и компонентов. Например, <h1>Hello</h1> является React элементом, записанный в JSX. Некоторые React элементы могут быть записан в виде JavaScript как React.DOM.h1 (NULL, 'Hello');. С JSX вы затратите меньше усилий, на чтение, запись и трансформацию в JavaScript перед запуском в браузере.

Виртуальный DOM является JavaScript деревом React элементов и компонентов. React создает виртуальный DOM для браузера, для вывода пользовательского интерфейса. React следить за виртуальным DOM для изменения и автоматического изменения в браузере, чтобы соответствовать виртуальному DOM.

С понимание этих понятий мы можем двигаться дальше к использованию React. Мы будем строить серию пользовательских интерфейсов, каждый из которых добавляет слой функциональности на предыдущий. Мы будем строить фотогалерею, похожую на Instagram.

Rendering

Первым делом рендерим визуальный элемент (React элементы или компоненты). Запомните, что виртуальный элемент существует только в памяти JavaScript, мы должны явно указать React, чтобы вывести его в браузере DOM.

React.render(<img src="http://tinyurl.com/lkevsb9" alt="" />, document.body);

Функция render принимает два аргумента: виртуальный элемент и реального DOM узла. React принимает визуальный элемент и вставляет его в данный DOM узлел. Изображение теперь отображается в браузере.

Компоненты

Компоненты сердце и душа React. Они настраиваемый React элементы. Они, как правило, расширены с уникальной функциональностью и структурой.

var Photo = React.createClass({
  render: function() {
    return <img src='http://tinyurl.com/lkevsb9' />
  }
});
React.render(<Photo />, document.body);

Функция createClass принимает объект, который реализует рендеринг функции.

Компонент photo создает <Photo />, и выводит его в теле документа.

Этот компонент не делает ничего, что делает предыдущий React элемент изображения, но он может быть расширен с пользовательскими функциями и структурами.

Свойства

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

var Photo = React.createClass({
  render: function() {
    return (
      <div className='photo'>
        <img src={this.props.imageURL} />
        <span>{this.props.caption}</span>
      </div>
    )
  }
});
React.render(<Photo imageURL='http://tinyurl.com/lkevsb9' caption='New York!' />, document.body);

Внутри функции React render: два параметра передаются в компонент Photo: ImageUrl и caption.

Внутри компонента функция render свойство ImageUrl используется в качестве SRC для React элемента изображения. caption также используется как обычный текст в React элементе span.

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

Состояние

Состояние объекта является внутренним для компонента. Он содержит данные, которые могут меняться с течением времени.

var Photo = React.createClass({
  toggleLiked: function() {
    this.setState({
      liked: !this.state.liked
    });
  },
  getInitialState: function() {
    return {
      liked: false
    }
  },
  render: function() {
    var buttonClass = this.state.liked ? 'active' : '';
    return (
      <div className='photo'>
        <img src={this.props.src} />
        <div className='bar'>
          <button onClick={this.toggleLiked} className={buttonClass}>
            ♥
          </button>
          <span>{this.props.caption}</span>
        </div>
      </div>
    )
  }
});
React.render(<Photo src='http://tinyurl.com/lkevsb9' caption='New York!'/>, document.body);

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

У компонента появилась новая функция getInitialState. React вызывает эту функцию, когда компонент инициализируется. Возвращаемый объект установлен в качестве начального состояния компонента (как имя функции).

Компонент имеет еще один новый функцию toggleLiked. Эта функция вызывает SetState для компонента, и меняет значение liked.

В визуализации функции компонента переменной buttonClass присваивается либо «активый» или стирается — в зависимости от liked состояния.

buttonClass используется в качестве имени класса для кнопки React элемента. Кнопка также имеет обработчик событий OnClick и настроена на работу в toggleLiked.

Вот что происходит, когда компонент оказывается в DOM браузера:

  • Когда кнопка компонента нажата, вызывается toggleLiked
  • liked состояние изменяется
  • React повторно рендерит компонент в виртуальный DOM
  • Новая виртуальная DОМ  сравнивается с предыдущим виртуальной DOM
  • React изолирует, что изменилось и обновляет DOM браузера

В этом случае, React изменяет имя класса на кнопке.

Состав

Состав означает объединение более мелких компонентов с образованием большего целого. Например компонент фотография может быть использовано в качестве компонента фотогалерии, например, так:

var Photo = React.createClass({
  toggleLiked: function() {
    this.setState({
      liked: !this.state.liked
    });
  },
  getInitialState: function() {
    return {
      liked: false
    }
  },
  render: function() {
    var buttonClass = this.state.liked ? 'active' : '';
    return (
      <div className='photo'>
        <img src={this.props.src} />
        <div className='bar'>
          <button onClick={this.toggleLiked} className={buttonClass}>
            ♥
          </button>
          <span>{this.props.caption}</span>
        </div>
      </div>
    )
  }
});
var PhotoGallery = React.createClass({
  getDataFromServer: function() {
    return [{
      url: 'http://tinyurl.com/lkevsb9',
      caption: 'New York!'
    },
    {
      url: 'http://tinyurl.com/mxkwh56',
      caption: 'Cows'
    },
    {
      url: 'http://tinyurl.com/nc7jv28',
      caption: 'Scooters'
    }];
  },
  render: function() {
    var data = this.getDataFromServer();
    var photos = data.map(function(photo) {
      return <Photo src={photo.url} caption={photo.caption} />
    });
    return (
      <div className='photo-gallery'>
        {photos}
      </div>
    )
  }
});
React.render(<PhotoGallery />, document.body);

Компонент Photo точно такой же, как и раньше.

Новый компонент PhotoGallery, который генерирует Photo компоненты. В этом случае есть некоторые поддельные данные сервер, который возвращает массив 3 объектов, содержащие url и caption.

Данные создают 3 компонента Photo, которые вставляются в возвращаемом значении render функции компонента.

Вывод

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

Создаем десктопное приложение с помощью HTML, JS и Node-WebKit

html5-node-webkit-retina-preview
image-532

Сейчас  вы можете создать очень многое с использование только JavaScript и HTML. Благодаря Node-WebKit, мы можем даже создать десктопные приложения которые имеют полный доступ к любой части операционной системы. В этой небольшой статье мы покажем вам, как создать простое приложение, используя Node-WebKit, который сочетает в себе JQuery и несколько Node.js модулей.

Node-WebKit является комбинацией Node.js и встроенным WebKit-браузером. Код JavaScript, который вы пишете выполняется в особой среде и имеет доступ к интерфейсу браузера и Node.js. Звучит интересно? Продолжайте читать!

Установка Node-Webkit

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

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

Первое приложение

Скачаем пример приложения. Запускаем через терминал приложение

# linux/osx
/path/to/node-webkit/nw /your/project/folder
# Iwindows
C:\path\to\node-webkit\nw.exe C:\your\project\folder

Структура пприложения

nw-app-tree
image-533

 

В итоге мы получаем готовое приложение.

Bower — быстрый старт

Сейчас очень много библиотек и фреймворков для френтед разработчиков, и уже не редкость когда в одном проекте пять или больше таких библиотек. И уследить за всеми, за новыми обновлениями становится все сложнее. Bower — пакетный менеджер от Twitter, он позволяет легко управлять всеми пакетами и зависимостями вашего проекта.

bower
image-356

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

Установка Bower

Bower может быть установлен с помощью npm, менеджер пакетов Node.js. Если у вас еще не установлен npm или Node.js. Установите их, npm входит в состав Node.js.

После того как вы установите npm, откройте терминал и введите следующую команду:

npm install -g bower

Это позволит установить Bower на вашу систему.

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

Поиск пакетов

Есть два способа, с помощью которых которые можно найти Bower пакеты. Либо с помощью онлайн каталога, или при помощи команды search.

bower search <имя пакета>

Например, для поиска пакетов, содержащих слово ‘JQuery’ вы могли бы сделать следующее:

bower search jquery

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

Search results:
    jquery git://github.com/components/jquery.git
    jquery-ui git://github.com/components/jqueryui
    jquery.cookie git://github.com/carhartl/jquery-cookie.git
    jquery-placeholder git://github.com/mathiasbynens/jquery-placeholder.git
    jquery-file-upload git://github.com/blueimp/jQuery-File-Upload.git
    jasmine-jquery git://github.com/velesin/jasmine-jquery
    jquery.ui git://github.com/jquery/jquery-ui.git
    ...

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

Установка пакетов

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

bower install <package>

Также вместо имени пакета, вы можете установить его, использууя один из следующих способов:

  • Адресс репозитория Git: //github.com/components/jquery.git
  • Путь в локальное хранилище Git
  • Сокращение components/jquery. Bower будет предполагать, что GitHub используется по умолчанию
  • URL к архивам. Содержание файлы будут извлечены автоматически.

Вы можете установить определенную версию пакета, добавив знак решётки (#) после имени пакета, за которым следует номер версии.

bower install <package>#<version>

Установленные пакеты будут размещены в каталоге bower_components. Вы можете изменить папку с помощью параметров конфигурации в файле .bowerrc.

  • bower_components
    • jquery
      • jquery.js
      • jquery.min.js
      • jquery.min.map
    • modernizr
      • modernizr.js

После установки, вы можете использовать пакет, просто добавив script или link теги в ваш HTML файл. Хотя Bower пакеты чаще всего содержат файлы JavaScript, они также могут содержать CSS или даже изображения.

<script src="path/to/bower_components/jquery/jquery.min.js"></script>

Установка пакетов с использованием bower.json файла

Если вы используете несколько пакетов в вашем проекте это то удобно их перечислить в файле bower.json. Это позволит вам устанавливать и обновлять несколько пакетов с помощью одной команды.

{
"name": "app-name",
"version": "0.0.1",
"dependencies": {
"sass-bootstrap": "~3.0.0",
"modernizr": "~2.6.2",
"jquery": "~1.10.2"
},
"private": true
}

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

Основные команды Bower

Список установленных пакетов в проекте

bower list

Обновление пакетов

bower update

Обновление пакетов

bower update

или обновление конкретного пакета

bower update <название пакета>

Удаление пакетов

bower uninstall <название пакета>

Удаление пакетов

bower uninstall <название пакета>