react.jsで広告テンプレートを作りたい #scripty03

30
©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止 React.jsで 広告テンプレートを 作りたい リッチラボ株式会社 穴井宏幸(@pirosikick) SCRIPTY#3 2015/03/10

Upload: yahoo

Post on 14-Jul-2015

3.016 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

React.jsで 広告テンプレートを

作りたいリッチラボ株式会社

穴井宏幸(@pirosikick) !

SCRIPTY#3 2015/03/10

Page 2: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

React.jsで 広告テンプレートを

作りたいリッチラボ株式会社

穴井宏幸(@pirosikick) !

SCRIPTY#3 2015/03/10

Page 3: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

React.jsで 広告テンプレートを

作りたいリッチラボ株式会社

穴井宏幸(@pirosikick) !

SCRIPTY#3 2015/03/10

リッチ広告

Page 4: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

穴井 宏幸 リッチラボ株式会社 エンジニア

@pirosikick(ぴろしきっく)

JavaScript, React.js, Flux Golangを始めたい

Page 5: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

話すこと

• リッチ広告でReact.jsを使いたい

• 検証のためいくつか書きなおした

• よかったことや

• あんまりよくなかったこと・実戦投入への課題など

Page 6: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

Page 7: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

/** * "Hello! ${ 入力内容 }"と表示するだけのサンプル */ import React from "react"; !let HelloApp = React.createClass({ getInitialState () { return { name: this.props.defaultName || '' }; }, ! render () { return ( <div className="wrapper"> <h1>Hello! { this.state.name }</h1> <input type="text" onChange={this.onChange}/> </div> ); }, ! onChange (e) { this.setState({ name: e.target.value }); } }); !React.render(<HelloApp defaultName="pirosikick" />, document.body);

Page 8: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

• View専門

• Virtual-DOM

• JSX(別に使わなくても書けるけども)

Page 9: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

リッチ広告

ちょっとデモ

Page 10: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

リッチ広告

• ユーザイベントで変化させてリッチな感じに

• ex) scroll, deviceorientation, touch, etc…

• CTRや広告の印象が良かったりする

Page 11: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

なんでReactで 作りたいか

• DOMを組み立てていく様が普段の開発と似ていてなんか相性良さそう

• 単純に新しいことをやりたい

Page 12: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

React.jsで 書き直してみた

バナープラス スクロールしている間だけ大きく表示される

Page 13: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

比較

Page 14: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

Before 163行 (独自ライブラリ除く)

Page 15: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

'use strict'; 'use es6'; !import React, { PropTypes } from 'react'; !let Banner = React.createClass({ propTypes: { src: PropTypes.string.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) }, ! render () { let props = this.props; let style = { width: props.width, height: props.height, margin: props.margin || '0 auto', position: 'relative', background: `transparent url(${props.src}) no-repeat 50% 50%`, backgroundSize: 'contain' }; ! return <div style={style}>{props.children}</div>; } }); !let Extension = React.createClass({ propTypes: { src: PropTypes.string.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, offsetTop: PropTypes.number.isRequired, offsetLeft: PropTypes.number.isRequired, shown: PropTypes.bool }, ! render () { let props = this.props; let style = { width: props.width, height: props.height, position: 'absolute', top: props.offsetTop, left: props.offsetLeft, zIndex: 100000, opacity: props.shown ? 1 : 0, background: `transparent url(${props.src}) no-repeat 50% 50%`, backgroundSize: 'contain', pointerEvents: 'none', webkitTransition: '-webkit-transform 0s linear', webkitTransform: 'translate3d(0, 0, 0)', webkitTransitionDelay: `${props.show ? 0 : 200}ms` }; ! return <div style={style}></div>; } }); !let Anchor = React.createClass({ propTypes: { href: PropTypes.string.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, clickTracking: PropTypes.string }, ! render () { let props = this.props; let style = { position: 'absolute', width: props.width, height: props.height, top: 0, left: 0 }; ! return <a href={props.href} onClick={this.onClick}></a>; }, ! onClick () { if (this.props.clickTracking) { var img = new Image(); img.src = this.props.clickTracking; } } }); !export default React.createClass({ displayName: 'BannerPlus', ! propTypes: { param: PropTypes.object.isRequired, option: PropTypes.object }, ! getInitialState () { let param = this.props.param; let option = this.props.option || {}; let state = {}; state.link = param.link; state.width = option.width || 320; state.height = option.height || 100; state.banner = { src: param['banner'], margin: option['margin'] || '0 auto' }; state.extension = { src: param['extension'], width: option['extensionWidth'] || 320, height: option['extensionHeight'] || 200, shown: false, }; state.extension.offsetTop = -(state.extension.height - state.height) * 0.5; state.extension.offsetLeft = -(state.extension.width - state.width) * 0.5; state.clickTracking = option['clickTracking']; state.researchTracking = option['researchTracking'] || []; ! return state; }, ! render () { let state = this.state; ! return ( <Banner src={state.banner.src} width={state.width} height={state.height} margin={state.banner.margin}> ! <Anchor href={state.link} width={state.width} height={state.height}></Anchor> ! <Extension src={state.extension.src} width={state.extension.width} height={state.extension.height} offsetTop={state.extension.offsetTop} offsetLeft={state.extension.offsetLeft} shown={state.extension.shown}></Extension> ! <div ref="hoge"></div> </Banner> ); }, ! showExtension () { if (!this.state.extension.shown) { this.state.extension.shown = true; this.setState({ extension: this.state.extension }); } }, ! hideExtension () { if (this.state.extension.shown) { this.state.extension.shown = false; this.setState({ extension: this.state.extension }); } }, ! showExtensionDuringScroll: (function() { let timerId; return function () { this.showExtension(); ! if (timerId) clearInterval(timerId); ! timerId = setInterval(this.hideExtension, 200); } })(), ! componentDidMount () { document.body.addEventListener('touchmove', this.showExtensionDuringScroll); window.addEventListener('scroll', this.showExtensionDuringScroll); }, ! componentWillUnmount () { document.body.removeEventListener('touchmove', this.showExtensionDuringScroll); window.removeEventListener('scroll', this.showExtensionDuringScroll); } });

After 185行 (本体除く)

• ちょっと増えた • 体感的にはそんなには書いていない

• propTypesの記述 • JSXを読みやすくするために

改行を多く入れたのが原因? • 実際はrequireとかで分けるだろう

Page 16: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

イベント処理記述

設定に基づきDOM構築

• ユーザイベントでstyle属性を書き換えるような処理

Before 全体の構成

Page 17: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

/** * DOMの構築 */ function $elem (elemName) { return $(document.createElement(elemName)); } !// CSSは使えないので直接style属性に定義 var banner $elem('div').css({ 'width': config.width, 'height': config.height, 'margin': config.margin, 'position': 'relative', 'background': `transparent url(${config.banner.src}) no-repeat 50% 50%`, 'backgroundSize': 'contain' }); !var extension = $elem('div').css(/* 割愛 */); !var anchor = $elem('a').css(/* 割愛 */); !anchor .attr('href', config.link) .on('click', function () { ... }; !banner.append(extension, anchor);

Before

※注) 実際のコードは晒せないのでjQueryで似たようなコードを書きました

Page 18: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

import React from "react"; !let Banner = React.createClass({ propTypes: {/* 割愛 */}, ! render () { let props = this.props; let style = { width: props.width, height: props.height, margin: props.margin || '0 auto', position: 'relative', background: `transparent url(${props.src}) no-repeat 50% 50%`, backgroundSize: 'contain' }; ! return <div style={style}>{props.children}</div>; } }); !// 上とstyle以外ほとんど一緒なので割愛 let Extension = React.createClass({/* 割愛 */}); let Anchor = React.createClass({/* 割愛 */});

After

• そんなに変わらない

Page 19: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

let BannerPlus = React.createClass({ propTypes: { param: ..., option: ... }, ! // this.propsに来た設定値の初期化・Validationなど getInitialState () { let param = this.props.param; let option = this.props.option; return { link: param.link, banner: { ... }, extension: { ... } }; }, ! render () { let state = this.state; return ( // this.stateを子Componentに渡してDOMを構築する <Banner src={state.banner.src} width={...} height={...}> <Anchor href={state.link} width={...} height={...} /> <Extension isShown={state.isExtensionShown} src={...} width={...} height={...} /> </Banner> ); }, ! // 外側の大きいバナーの表示・非表示 showExtension () {/* 後述 */}, hideExtension () {/* 後述 */} }); !// 広告描画 var param = { ... }, option = { ... } React.render(<BannerPlus param={param} option={option} />, target);

After

Page 20: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

※注) 実際のコードは晒せないのでjQueryで似たようなコードを書きました

Before/** * ユーザイベントに合わせてstyle属性の書き換え */ !var isExtensionShown = false; var timerId; !// 大きいバナーを表示する処理 function showExtension () { extension.css({ /* 割愛 */ }); isExtensionShown = true; } !// 多きバナーを隠す処理 function hideExtension () { dom.extension.css({ /* 割愛 */ }); isExtensionShown = false; } !// スクロール時に表示・非表示 $(window).on('scroll', function () { !isExtensionShown && showExtension(); ! if (timerId) clearInterval(timerId); ! timerId = setInterval(function () { hideExtension(); }, 200); });

Page 21: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

let BannerPlus = React.createClass({ render () { /* 割愛 */ }, ! // state.extension.shownの切り替えで // 子Componentのstyleを切り替える showExtension () { if (!this.state.isExtensionShown) this.setState({ isExtensionShown: true }); }, ! hideExtension () { if (this.state.isExtensionShown) this.setState({ isExtensionShown: false }); }, ! onScroll () { !this.state.isExtensionShown && this.showExtension(); ! if (this._timerId) clearInterval(this._timerId); ! this._timerId = setInterval(function () { this.hideExtension(); }.bind(this), 200); } ! componentDidMount () { window.addEventListener('scroll', this.onScroll); }, ! componentWillUnmount () { window.removeEventListener('scroll', this.onScroll); } });

After

Page 22: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

/** * 外側の大きいバナーのComponent */ !let Extension = React.createClass({ propTypes: { /* 割愛 */ }, ! render () { let props = this.props; let style = { ! /* 省略 */ ! /** * props.isShownで表示・非表示 */ opacity: props.isShown ? 1 : 0, ! /* 省略 */ ! }; ! return <div style={style}></div>; } });

After

Page 23: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

良い点

Page 24: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

• JSX HTMLを書く感じで心地良い

• 全体の見通しがよくなった

• Componentに見た目と振る舞いの定義があるから

• 単体テストしやすい

• this.props

• React.addons.TestUtils

Page 25: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

実戦投入への課題

Page 26: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

うまく 実装できなかったこと

• 広告を挿入する要素の外側への処理

• ex) expand時にbodyに要素を付け替え

• 実装できてもあんまりいいやり方では無さそう

• 2回React.renderを呼び、予めbodyに要素追加

• Reactの外から要素を移動して戻す

Page 27: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

サイズがデカイ

• react.min.js v0.12.2 128KB

• 大体10KBに収まるように心がけているので、めっちゃでかい。。。

• 他のVDOM系もそれなりのサイズ感

• deku.min.js 9.9KB

• virtual-dom 28KB(uglifyjs)

Page 28: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

まとめ

Page 29: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

• リッチ広告への実戦投入は現状出来なそう🙅

• 容量が小さいものが欲しい

• 書いている時の気持ちよさ👏 (JSXに限る)

• コードの見通しがよく感じた👍

Page 30: React.jsで広告テンプレートを作りたい #scripty03

©2015 Rich Lab Co., Ltd. All Rights Reserved. 無断利用・転載禁止

Thanks:)

@pirosikick(ぴろしきっく)