os desafios do desenvolvimento
de front-end em um e-commerce
@shiota 2013
olá!slideshare.net/eshiota
github.com/eshiota
@shiota
front-end engineer
@
DEV
e-commerce 101
em alguns slides
=)
taxa de conversão
dos usuários que entram no site, quantos
finalizam uma compra?
ticket médio
em média, quanto os usuários gastam
por compra?
=)
taxa de conversão
×
ticket médio
=
taxa de conversão
×
ticket médio
= $
= $=)
= ?=)
complexo
indeciso
exigente
inexperiente
decidido
cuidadoso
experiente
ser humano, como
todos nós =)
o que faz o usuário
abandonar o carrinho?
alto custo de frete
$
não estão prontos
para finalizar
?
produtos muito caros
$
guardam para depois
não mencionou
claramente o frete
?
sem guest checkout
formulário com
muitas informações
checkout complexo
website lento
taxas extras
falta de opções
de pagamento
entrega demorada
spam de ofertas
site não funciona
=(
como o front-end pode
melhorar a conversão?
formulário com muitas informações
checkout complexo
website lento
site não funciona
velocidade da página
interface estável
detalhes = emotion design
validação de novas hipóteses
desafios de front-end
(agora a palestra começa =P)
múltiplos desenvolvedores
desenvolvimento escalável
performance client-side
testes a/b
trabalhando com
vários desenvolvedores
trabalhar em equipe
é difícil... =(
aspas simples
×
aspas duplas
ponto e vírgula no JS
×
sem ponto e vírgula
JavaScript
×
CoffeeScript
(JavaScript, claro)
(com ponto e vírgula)
... mas cada um pode ter
uma visão diferente e
complementar. =)
code smell performance
sintaxe arquitetura
mantenha um code
standard para o time.
consistência, legibilidade,
sem bikeshed.
git + pull requests
qualquer um revisa,
qualquer um comenta.
diferentes visões,
mais erros detectados.
o conhecimento é
disseminado pelo time.
todos ficam mais
criteriosos com o que fazem.
desenvolvimento
escalável e testável
desenvolvimento ágil:
mudanças precisas,
altos ganhos.
melhorias são constantes,
e nada é 100% definitivo.
o código deve ser facilmente
alterável/adaptável.
dica 1
usem pre-processors de CSS.
sério. agora. já. eu espero.
sass
+
variáveis, mixins e
funções
/*********************************************************************
*
* Variables Module
*
* All constants that will be used through the styles must be
* defined here.
*
*********************************************************************/
$TEXT_COLOR : #555;
$DISCOUNT_COLOR : #ef6565;
$LIGHT_COLOR : #fefafa;
$SELECTION_BACKGROUND : #41bdce;
$SELECTION_COLOR : #fff;
$LINK_COLOR : #447f87;
$LINK_HOVER_COLOR : #41bdce;
$LINK_ACTIVE_COLOR : #447f87;
$ERROR_BACKGROUND : #fffaad;
$LIGHT_BACKGROUND : #fefefa;
$SITE_WIDTH : 978px;
$FOOTER_HEIGHT : 777px;
$PURPLE : #905194;
$ORANGE : #fbb100;
/*********************************************************************
*
* Mixins Module
*
* All general purpose mixins are defined here.
*
*********************************************************************/
/*********************************************************************
* =Clearfix
*********************************************************************/
@mixin clearfix {
&:after {
clear: both;
content: " ";
display: block;
font-size: 0;
height: 0;
visibility: hidden;
}
zoom: 1;
}
/*********************************************************************
* =Image replacement
*
* `display` property should be declare on the element, not here
* on the mixin. Element must have fixed width and height.
*********************************************************************/
@mixin img_replacement {
text-indent: 100%;
overflow: hidden;
white-space: nowrap;
}
/*********************************************************************
*
* Functions Module
*
* Custom functions used by the application
*
*********************************************************************/
// Returns unitless number
@function remove-unit($number) {
$unit: unit($number);
$one: 1;
@if $unit == "px" { $one: 1px; }
@if $unit == "em" { $one: 1em; }
@if $unit == "%" { $one: 1%; }
@return $number / $one;
}
// Returns flexible value
// Returns `em` by default, accepts `%` as format.
@function flex($target, $context: 14, $unit: "em") {
$size: remove-unit($target) / remove-unit($context);
@if $unit == "em" { @return #{$size}em; }
@if $unit == "%" { @return percentage($size); }
}
// Alias to `flex` function, using `%` as format.
@function perc($target, $context) {
@return flex($target, $context, "%");
}
// Alias to `flex` function, using `em` as format.
@function em($target, $context: 14) {
@return flex($target, $context, "em");
}
estilos modularizados
em partials
app/
assets/
stylesheets/
base/
_functions.scss
_mixins.scss
_variables.scss
ui/
_breadcrumb.scss
_carousel.scss
_dentedBox.scss
_flashMessage.scss
/*******************************************************************************
*
* UI > Breadcrumb
*
* General styles for the breadcrumb.
*
*******************************************************************************/
.breadcrumb {
font-size: em(12px);
line-height: em(21px, 12px);
text-transform: uppercase;
color: #444;
width: 978px;
}
.breadcrumb a,
.breadcrumb a:visited,
.breadcrumb a:active {
color: #444;
text-decoration: none;
}
.breadcrumb a:hover {
color: #444;
text-decoration: underline;
}
.breadcrumb .separator {
padding: 0 3px;
}
/*******************************************************************************
*
* UI > Loader
*
* Animated loader for AJAX requests
*
*******************************************************************************/
@mixin loader_sprite_position($xoffset, $yoffset) {
background-position: sprite-position($icon-sprite, loader_sprite, $xoffset, $yoffset);
}
.loader {
width: 25px;
height: 25px;
display: none;
}
.loader b {
display: block;
width: 25px;
height: 25px;
background-image: sprite-url($icon-sprite);
}
.loader b,
.loader .f1 { @include loader_sprite_position(-10px, -10px); }
.loader .f2 { @include loader_sprite_position(-45px, -10px); }
.loader .f3 { @include loader_sprite_position(-80px, -10px); }
.loader .f4 { @include loader_sprite_position(-115px, -10px); }
.loader .f5 { @include loader_sprite_position(-150px, -10px); }
.loader .f6 { @include loader_sprite_position(-185px, -10px); }
.loader .f7 { @include loader_sprite_position(-220px, -10px); }
.loader .f8 { @include loader_sprite_position(-255px, -10px); }
geração automática de sprites
acelera o desenvolvimento.
$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);
$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);
/* Compass sprite function receives the map variable and image as arguments */
background: sprite($icon-sprite, arrow_dropdown) no-repeat;
/* Compiled CSS */
background: url(/assets/icon-s5dab8c2901.png) -40px -158px no-repeat;
função de inline image
economiza requests.
/* Compiled CSS */
background: #f5f3fb url('data:image/
png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQCAMAAAAcVM5PAAAAGXRFWHRTb2Z0d2FyZQBBZG9
iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF5+TW////////4qZUpQAAAAN0Uk5T//
8A18oNQQAAACBJREFUeNpiYGBgAgMGBkYog4mJXAbILAiDkVxzAAIMAEMOAPMId2OWAAAAAElFTkSuQmCC
') repeat;
/* Generates a base64 image */
background: #f5f3eb inline-image("bg_dots.png") repeat;
(seja criterioso)
dica 2
módulos: poucas linhas,
comportamentos isolados,
extensíveis, e testáveis.
estrutura base (reset, base styles)
grid
padrões
módulos
módulos contextualizados
css do módulo
/*******************************************************************************
*
* UI > Loader
*
* Animated loader for AJAX requests
*
*******************************************************************************/
@mixin loader_sprite_position($xoffset, $yoffset) {
background-position: sprite-position($icon-sprite, loader_sprite, $xoffset, $yoffset);
}
.loader {
width: 25px;
height: 25px;
display: none;
}
.loader b {
display: block;
width: 25px;
height: 25px;
background-image: sprite-url($icon-sprite);
}
.loader b,
.loader .f1 { @include loader_sprite_position(-10px, -10px); }
.loader .f2 { @include loader_sprite_position(-45px, -10px); }
.loader .f3 { @include loader_sprite_position(-80px, -10px); }
.loader .f4 { @include loader_sprite_position(-115px, -10px); }
.loader .f5 { @include loader_sprite_position(-150px, -10px); }
.loader .f6 { @include loader_sprite_position(-185px, -10px); }
.loader .f7 { @include loader_sprite_position(-220px, -10px); }
.loader .f8 { @include loader_sprite_position(-255px, -10px); }
css do módulo
contextualizado
// On ui/_buttons.scss
.bt-wrapper .loader {
position: absolute;
z-index: 4;
right: 20px;
top: 50%;
margin-top: -9px;
}
// On modules/_checkoutAddressForm.scss
.address-form .cep-input .loader {
position: absolute;
right: -33px;
top: em(29px);
}
javascript enxuto,
auto-contido.
// Implements the animated loader for AJAX requests
// Loader constructor
//
// * `placement`: Function that determines the loader's placement
ns("EDEN.ui.Loader", function (placement) {
if (!(this instanceof EDEN.ui.Loader)) {
return new EDEN.ui.Loader(placement);
}
this.frame = 1;
this.framesQty = 8;
this.stack = [];
this.animating = false;
this.$loader = $("<div class='loader'><b> </b></div>");
this.$renderer = this.$loader.find("b");
this.placement = placement;
this.init();
});
EDEN.ui.Loader.prototype = {
// Properties
// ----------
// Animation speed (in frames per second)
fps : 20,
// Fading speed
fadeSpeed : 150,
// Public methods
// --------------
testável!
describe("EDEN.ui.Loader", function () {
var Loader = EDEN.ui.Loader;
beforeEach(function () {
loadFixtures("loader.html");
});
afterEach(function () {
$("body").find(".loader").remove();
});
it("accepts instance creation without new operator", function () {
var newLoader = Loader();
expect(newLoader).toBeInstanceOf(Loader);
});
it("inits the loader on creation", function () {
var loader
, oldInit = EDEN.ui.Loader.prototype.init
;
EDEN.ui.Loader.prototype.init = jasmine.createSpy();
loader = new Loader();
expect(loader.init).toHaveBeenCalled();
EDEN.ui.Loader.prototype.init = oldInit;
});
it("appends the loader to body as a default", function () {
var loader = new Loader();
expect($("body").find(".loader").length).toEqual(1);
});
it("appends the loader through an argument function", function () {
var loader = new Loader(function ($loader) {
$("#loader-placeholder").append($loader);
});
expect($("#loader-placeholder").find(".loader").length).toEqual(1);
});
});
"Mas tem muita coisa que
não dá pra testar, né?"
"Mas testes atrapalham a
entrega do projeto, né?"
a Baby possui 1144 specs de
JavaScript até agora
falhas no jshint ou nas specs
de javascript quebram o build
dica 3
javascript desacoplado e
modularizado
mediator: ponto central de
comunicação via pub/sub
MEDIATOR
nenhum módulo tem
conhecimento do outro
MEDIATOR
Mediator, me avisa quando
sair o novo do Game of
Thrones?
Blz
MEDIATOR
Mediator, me avisa quando
sair o novo do Mythbusters?
É nóish.
MEDIATOR
Mediator, saiu um eppy novo
de Game of Thrones.
Subscribers, saiu um eppy
novo de Game of Thrones!
Ae, vou baixar, acho
que vai ser feliz e tal
=D
MEDIATOR
Mediator, saiu um eppy novo
de Mythbusters.
Subscribers, saiu um eppy
novo de Mythbusters!
Ae, vou baixar!
os módulos só conhecem
o mediator
módulos desacoplados, com
comportamentos específicos e
isolados
// Code inside ShippingAddressForm
_registerInterests : function () {
this.element.find(".cep-input")
.on("keyup paste cut", this._onCepModification.bind(this));
},
_onCepModification : function (event) {
if (this.isCepFilled()) {
EDEN.mediator.trigger("shipping-cep-change", event.target.value);
} else {
EDEN.mediator.trigger("shipping-cep-incomplete", event.target.value);
}
}
// Code inside checkoutModule
_registerInterests : function () {
EDEN.mediator.on("shipping-cep-change", this._onShippingCepChange, this);
this.shippingService.on("get-success", this._onShippingGetSuccess, this);
},
_onShippingCepChange : function (cep) {
this.shippingService.get(cep);
}
_onShippingGetSuccess : function (data) {
EDEN.mediator.trigger("shipping-rate-change", data.rate);
EDEN.mediator.trigger("delivery-estimate-change", data.estimate);
}
// Code inside purchseInfo
_registerInterests : function () {
EDEN.mediator.on("shipping-rate-change", this._onShippingRateChange, this);
EDEN.mediator.on("delivery-estimate-change", this._onDeliveryEstimateChange, this);
},
_onShippingRateChange : function (rate) {
this.updateShippingRate(rate);
},
_onDeliveryEstimateChange : function (days) {
this.updateDeliveryEstimate(days);
},
updateShippingRate : function (rate) {
var formatter = EDEN.currency.formatter;
this.element.find(".shipping-rate").text(formatter(rate));
this.shippingRate = rate;
this.updateTotal();
},
updateTotal : function () {
var total = this.subtotal + this.shippingRate,
formatter = EDEN.currency.formatter;
this.element.find(".total").text(formatter(total));
EDEN.mediator.trigger("cart-total-change", total);
}
// Code inside installmentSelector
_registerInterests : function () {
EDEN.mediator.on("cart-total-change", this._onCartTotalChange, this);
},
_onCartTotalChange : function (total) {
this.updateInstallments(total);
},
updateInstallments : function (total) {
// Updates the values
}
você não precisa saber
tudo isso de primeira.
addyosmani.com/largescalejavascript
aprenda javascript antes de
se focar em um framework.
performance
client-side
css/javascript
minification/compression
lazy-load everything! o/
sprites e imagens inlines
sass
+
não abuse de font-faces
testes a/b
isole os estilos e JS em
classes, partials e módulos
totalmente separados
<nav id="site-menu" class="site-menu">
<div class="site-menu-container">
<% if new_header? %>
<%= render "layouts/open_site_nav" %>
<% else %>
<%= render "layouts/site_nav" %>
<% end %>
<% unless new_header? %>
<%= render "layouts/search" %>
<% end %>
</div>
</nav>
/*******************************************************************************
* =Menu A
*******************************************************************************/
.site-header-old .user-menu {
position: absolute;
right: perc(261px, $SITE_WIDTH);
cursor: pointer;
width: 213px;
height: 63px;
overflow: hidden;
z-index: 600;
}
/*******************************************************************************
* =Menu B
*******************************************************************************/
.site-header-new .user-menu {
position: absolute;
right: perc(261px, $SITE_WIDTH);
width: perc(150px, $SITE_WIDTH);
height: em(63px);
overflow: hidden;
z-index: 600;
}
AB-TESTING.md - como
remover a versão perdedora
# A/B Testing on Baby Site
This document lists all A/B tests currently being run on the project, and
shortly introduces the method being used.
## Tests currently being run
### Site-wide
#### Header design version
* Test name: `header-version`
* Starts at: `ApplicationController`, on `:before_filter`
* Goal: When user goes to a success checkout page
* Ends at: `orders#success` view
* PR/Commits: [#664](https://github.com/Baby-com-br/troy/pull/664)
To remove this test:
* Remove the `new_header?` method and its `:helper_method` on
`application_controller.rb`
* Remove the `header_version` method and its `:helper_method` on
`application_controller.rb` and ALL its calls.
* Consolidate the correct `render` calls on `layouts/_header.html.erb` and
`layouts/_site_menu.html.erb`
* Remove the `site-header-<%= header_version %>` class on `layouts/_header.html.erb`
* Remove the `header-version-<%= header_version %>` class on `layouts/_head.html.erb`,
on the `<body>` tag
* Remove the `finished` call on `baby-site/app/views/orders/success.html.erb`
* On `modules/_mainSearchForm.scss`, remove the entire block related to the
loser version, and on the winner version: (1) remove the comment header about
the A/B test, (2) unprefix all selectors by removing either `.site-menu` (if
the old header won) or `.site-header` (if the new header won)
* On `layout/_user_menu.scss`, remove the entire block related to the
loser version, and on the winner version: (1) remove the comment header about
the A/B test, (2) unprefix all selectors by removing either `.site-header-new` (if
the old header won) or `.site-header-old` (if the new header won)
* On `ui/_section_header.scss`, remove the `.header-version-old .section-titles`
and `.header-version-new .section-titles` blocks, and use the winner padding
on `.section-titles`.
* On `sections/_profile.scss`, remove the `.header-version-old .profile-header .site-menu`
and `.header-version-new .profile-header .site-menu` blocks, and use the winner padding
on `.profile-header .site-menu`.
* On `layout/_main.scss`, delete the `.header-version-old .site-menu-container` block.
shiota, um dev front-end
precisa saber back-end?
fulano(a), eu preciso saber
cozinhar ou lavar roupa?
não, mas ajuda, né? ;D
você não precisa ser um
nando vieira*.
* @fnando - faz design, front-end, manja JS pacas, é um dev Ruby f*odido, e manja de SysOps
saber back-end
melhora seu código.
saber back-end
lhe dá mais controle.
saber back-end
melhora a comunicação.
quando você deixa de perguntar
apenas "como vou fazer isso" e
passa a perguntar "como vou
fazer isso da melhor maneira"...
... você está no
caminho certo.
divirta-se. sempre. =)
obrigado!slideshare.net/eshiota
github.com/eshiota
@shiota

Desafios do Desenvolvimento de Front-end em um e-commerce