Podsumowanie
Jak użyliśmy Polymeru do stworzenia wydajnej mobilnej technologii WebGL Miecz świetlny, który jest modułowy i można skonfigurować. Przyjrzymy się najważniejszym szczegółom naszego projektu https://lightsaber.withgoogle.com/, aby zaoszczędzić Ci czas, gdy następnym razem natkniesz się na grupę wściekłych Szturmowców.
Omówienie
Jeśli zastanawia się Pan/Pani, czym są Polymer lub WebKomponent, najlepiej jest zacząć od udostępnienia fragmentu z faktycznego działającego projektu. Oto przykład wzięty ze strony docelowej naszego projektu https://lightsaber.withgoogle.com. Jest to zwykły plik HTML, który ma w środku trochę magii:
<!-- Element-->
<dom-module id="sw-page-landing">
<!-- Template-->
<template>
<style>
<!-- include elements/sw/pages/sw-page-landing/styles/sw-page-landing.css-->
</style>
<div class="centered content">
<sw-ui-logo></sw-ui-logo>
<div class="connection-url-wrapper">
<sw-t key="landing.type" class="type"></sw-t>
<div id="url" class="connection-url">.</div>
<sw-ui-toast></sw-ui-toast>
</div>
</div>
<div class="disclaimer epilepsy">
<sw-t key="disclaimer.epilepsy" class="type"></sw-t>
</div>
<sw-ui-footer state="extended"></sw-ui-footer>
</template>
<!-- Polymer element script-->
<script src="scripts/sw-page-landing.js"></script>
</dom-module>
Jeśli chcesz utworzyć aplikację opartą na HTML5, masz do wyboru wiele opcji. Interfejsy API, platformy, biblioteki, silniki gier itp. Konfiguracja jest trudna do skonfigurowania, mimo że jest tu wiele możliwości między kontrolą nad wysoką wydajnością grafiki a przejrzystą modułową do ich struktury i skalowalności. Odkryliśmy, że technologia Polymer może pomóc nam utrzymać projekt, który jest zorganizowany przy jednoczesnej pracy na niskim poziomie. i staraliśmy się opracować sposób, w jaki zrealizowaliśmy nasz projekt, aby jak najlepiej wykorzystać możliwości platformy Polymer.
Modułowe działanie w przypadku Polymer
Polymer to biblioteka, która umożliwia daje większą kontrolę nad sposobem tworzenia projektu z wykorzystaniem elementów niestandardowych wielokrotnego użytku. Umożliwia korzystanie z samodzielnych, w pełni funkcjonalnych modułów znajdujących się w jednym pliku HTML. Mają nie tylko strukturę (znaczniki HTML), ale też style wbudowane i logika.
Spójrz na ten przykład:
<link rel="import" href="bower_components/polymer/polymer.html">
<dom-module id="picture-frame">
<template>
<!-- scoped CSS for this element -->
<style>
div {
display: inline-block;
background-color: #ccc;
border-radius: 8px;
padding: 4px;
}
</style>
<div>
<!-- any children are rendered here -->
<content></content>
</div>
</template>
<script>
Polymer({
is: "picture-frame",
});
</script>
</dom-module>
Jednak w większym projekcie pomocne może być oddzielenie tych 3 elementów logicznych: (HTML, CSS, JS) i scalać tylko podczas kompilacji. Jedna rzecz każdy element projektu ma oddzielny folder:
src/elements/
|-- elements.jade
`-- sw
|-- debug
| |-- sw-debug
| |-- sw-debug-performance
| |-- sw-debug-version
| `-- sw-debug-webgl
|-- experience
| |-- effects
| |-- sw-experience
| |-- sw-experience-controller
| |-- sw-experience-engine
| |-- sw-experience-input
| |-- sw-experience-model
| |-- sw-experience-postprocessor
| |-- sw-experience-renderer
| |-- sw-experience-state
| `-- sw-timer
|-- input
| |-- sw-input-keyboard
| `-- sw-input-remote
|-- pages
| |-- sw-page-calibration
| |-- sw-page-connection
| |-- sw-page-connection-error
| |-- sw-page-error
| |-- sw-page-experience
| `-- sw-page-landing
|-- sw-app
| |-- bower.json
| |-- scripts
| |-- styles
| `-- sw-app.jade
|-- system
| |-- sw-routing
| |-- sw-system
| |-- sw-system-audio
| |-- sw-system-config
| |-- sw-system-environment
| |-- sw-system-events
| |-- sw-system-remote
| |-- sw-system-social
| |-- sw-system-tracking
| |-- sw-system-version
| |-- sw-system-webrtc
| `-- sw-system-websocket
|-- ui
| |-- experience
| |-- sw-preloader
| |-- sw-sound
| |-- sw-ui-button
| |-- sw-ui-calibration
| |-- sw-ui-disconnected
| |-- sw-ui-final
| |-- sw-ui-footer
| |-- sw-ui-help
| |-- sw-ui-language
| |-- sw-ui-logo
| |-- sw-ui-mask
| |-- sw-ui-menu
| |-- sw-ui-overlay
| |-- sw-ui-quality
| |-- sw-ui-select
| |-- sw-ui-toast
| |-- sw-ui-toggle-screen
| `-- sw-ui-volume
`-- utils
`-- sw-t
Foldery każdego elementu mają taką samą strukturę wewnętrzną z osobnymi katalogi i pliki logiczne (pliki kawa), style (pliki CSS) oraz (plik jade).
Oto przykładowy element sw-ui-logo
:
sw-ui-logo/
|-- bower.json
|-- scripts
| `-- sw-ui-logo.coffee
|-- styles
| `-- sw-ui-logo.scss
`-- sw-ui-logo.jade
Jeśli spojrzysz na plik .jade
:
// Element
dom-module(id='sw-ui-logo')
// Template
template
style
include elements/sw/ui/sw-ui-logo/styles/sw-ui-logo.css
img(src='[[url]]')
// Polymer element script
script(src='scripts/sw-ui-logo.js')
Możesz zobaczyć, jak wszystko jest zorganizowane w przejrzysty sposób, dodając style
i logikę w osobnych plikach. Aby włączyć nasze style do
korzystamy z instrukcji include
Jade, więc mamy wbudowany kod CSS
zawartości pliku po skompilowaniu. Element skryptu sw-ui-logo.js
będzie
uruchamianych w czasie działania.
Zależności modułowe w Bower
Zwykle zapisujemy biblioteki i inne zależności na poziomie projektu.
Jednak w powyższej konfiguracji zobaczysz element bower.json
, który znajduje się
folder elementów: zależności na poziomie elementu. Idea tego podejścia
jest to sytuacja, w której mamy wiele elementów o różnych
możemy upewnić się, że ładują się tylko te zależności, które są
w rzeczywistości. Jeśli usuniesz jakiś element, nie musisz pamiętać,
usuń zależność, ponieważ usuniesz też plik bower.json
deklarującej te zależności. Każdy element niezależnie wczytuje
zależności między nimi.
Aby jednak uniknąć powielania zależności, plik .bowerrc
jest też dołączany do folderu każdego elementu. Informuje, gdzie przechowywać dane
aby mieć pewność, że w kodzie w hierarchii jest tylko jedna zależności
katalogu:
{
"directory" : "../../../../../bower_components"
}
Dzięki temu, jeśli wiele elementów deklaruje THREE.js
jako zależność, raz
Bower instaluje go dla pierwszego elementu i zaczyna analizować drugi,
zauważy, że ta zależność jest już zainstalowana i nie będzie
pobierz go jeszcze raz lub zduplikuj. Zachowa ona również tę zależność
plików, o ile jest co najmniej jeden element, który nadal je definiuje
to bower.json
.
Skrypt bash znajduje wszystkie pliki bower.json
w strukturze elementów zagnieżdżonych.
Następnie kolejno wchodzi do tych katalogów i wykonuje funkcję bower install
każdy z nich:
echo installing bower components...
modules=$(find /vagrant/app -type f -name "bower.json" -not -path "*node_modules*" -not -path "*bower_components*")
for module in $modules; do
pushd $(dirname $module)
bower install --allow-root -q
popd
done
Szybki nowy szablon elementu
Za każdym razem, gdy chcesz utworzyć nowy element, może minąć trochę czasu: generowanie z podstawową strukturą plików i właściwymi nazwami. Wykorzystujemy więc Slush, aby napisać prosty generator elementów.
Możesz wywołać ten skrypt z poziomu wiersza poleceń:
$ slush element path/to/your/element-name
Tworzony jest nowy element, w tym cała struktura pliku i jego zawartość.
Zdefiniowaliśmy szablony dla plików elementów, np. szablon pliku .jade
wygląda tak:
// Element
dom-module(id='<%= name %>')
// Template
template
style
include elements/<%= path %>/styles/<%= name %>.css
span This is a '<%= name %>' element.
// Polymer element script
script(src='scripts/<%= name %>.js')
Generator Slush zastępuje zmienne rzeczywistymi ścieżkami i nazwami elementów.
Używanie Gulp do tworzenia elementów
Gulp zapewnia kontrolę procesu kompilacji. W naszej strukturze, elementy, które Gulp musi wykonać, aby wykonać te czynności:
- Kompiluj elementy
.coffee
plików do folderu.js
- Kompiluj elementy
.scss
plików do folderu.css
- Kompiluj elementy
.jade
do.html
z umieszczonymi plikami.css
.
Więcej informacji:
Kompilowanie plików .coffee
elementów do pliku .js
gulp.task('elements-coffee', function () {
return gulp.src(abs(config.paths.app + '/elements/**/*.coffee'))
.pipe($.replaceTask({
patterns: [{json: getVersionData()}]
}))
.pipe($.changed(abs(config.paths.static + '/elements'), {extension: '.js'}))
.pipe($.coffeelint())
.pipe($.coffeelint.reporter())
.pipe($.sourcemaps.init())
.pipe($.coffee({
}))
.on('error', gutil.log)
.pipe($.sourcemaps.write())
.pipe(gulp.dest(abs(config.paths.static + '/elements')));
});
W krokach 2 i 3 używamy gulpa i wtyczki compass do kompilowania scss
na .css
oraz .jade
na .html
w sposób podobny do opisanego powyżej.
W tym elementy polimerowe
Aby uwzględnić elementy Polymer, używamy importu HTML.
<link rel="import" href="elements.html">
<!-- Polymer -->
<link rel="import" href="../bower_components/polymer/polymer.html">
<!-- Custom elements -->
<link rel="import" href="sw/sw-app/sw-app.html">
<link rel="import" href="sw/system/sw-system/sw-system.html">
<link rel="import" href="sw/system/sw-routing/sw-routing.html">
<link rel="import" href="sw/system/sw-system-version/sw-system-version.html">
<link rel="import" href="sw/system/sw-system-environment/sw-system-environment.html">
<link rel="import" href="sw/pages/sw-page-landing/sw-page-landing.html">
<link rel="import" href="sw/pages/sw-page-connection/sw-page-connection.html">
<link rel="import" href="sw/pages/sw-page-calibration/sw-page-calibration.html">
<link rel="import" href="sw/pages/sw-page-experience/sw-page-experience.html">
<link rel="import" href="sw/ui/sw-preloader/sw-preloader.html">
<link rel="import" href="sw/ui/sw-ui-overlay/sw-ui-overlay.html">
<link rel="import" href="sw/ui/sw-ui-button/sw-ui-button.html">
<link rel="import" href="sw/ui/sw-ui-menu/sw-ui-menu.html">
Optymalizacja pierwiastków polimerowych do produkcji
Duży projekt może zawierać wiele elementów Polymer. W naszym
tych projektów jest ponad pięćdziesiąt. Jeśli weźmiemy pod uwagę, że każdy element ma osobny plik .js
, a niektóre z nich odwołują się do bibliotek, okaże się, że jest to ponad 100 osobnych plików. Oznacza to, że przeglądarka musi wysyłać wiele żądań,
z utratą skuteczności. Podobnie jak w procesie konkatenacji i minifikacji
w przypadku kompilacji Angular, „wulkanizujemy” projekt Polymer
do produkcji.
Vulcanize to narzędzie polimerowe, które spłaszcza drzewo zależności do pojedynczego pliku HTML, zmniejszając liczby żądań. Jest to szczególnie przydatne w przeglądarkach, które nie natywną obsługę komponentów sieciowych.
CSP (Content Security Policy) i Polymer
Podczas tworzenia bezpiecznych aplikacji internetowych należy wdrożyć CSP. CSP to zestaw reguł, które zapobiegają atakom typu cross-site scripting (XSS): uruchamianie skryptów z niebezpiecznych źródeł lub wykonywanie wbudowanych skryptów z plików HTML.
Wygenerowano ten zoptymalizowany, połączony i zminifikowany plik .html
.
firmy Vulcanize zawiera kod JavaScript w źródle niezgodnym z CSP.
. Aby go rozwiązać, używamy narzędzia
Wyraźniejszy.
Crisper rozdziela wbudowane skrypty z pliku HTML i umieszcza je w jednym,
zewnętrznego pliku JavaScript pod kątem zgodności z CSP. W tym samym czasie mijamy
pliki HTML za pomocą Crisper. Zostaną utworzone 2 pliki: elements.html
oraz
elements.js
W ramach elements.html
odpowiada też za wczytywanie wygenerowanego elements.js
.
Struktura logiczna aplikacji
W Polymer pierwiastki mogą być wszystko, od niewizualnej użyteczności po małe, samodzielnych i wielokrotnego użytku elementów interfejsu (np. przycisków) i większych modułów, takich jak „strony” a nawet tworzyć pełne aplikacje.
Przetwarzanie końcowe z użyciem architektury Polymer i architektura nadrzędny-podrzędny
W każdym potoku graficznym 3D zawsze jest ostatni etap, w którym efekty są dodawane do całego zdjęcia jako nakładki. To jest i obejmuje takie efekty, jak poświaty, promienie boskie, głębi ostrości, efektu bokeh, rozmycia itp. Efekty są łączone i stosowane do różne elementy w zależności od sposobu budowania sceny. W THREE.js może utworzyć niestandardowy program do cieniowania do przetwarzania końcowego w JavaScripcie lub jest to możliwe dzięki strukturze Polymer.
Jeśli spojrzymy na kod HTML elementu pośredniczącego:
<dom-module id="sw-experience-postprocessor">
<!-- Template-->
<template>
<sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
<sw-experience-effect-dof class="effect"></sw-experience-effect-dof>
<sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
</template>
<!-- Polymer element script-->
<script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>
Efekty określamy jako zagnieżdżone elementy Polymer w ramach wspólnej klasy. Następnie:
w sw-experience-postprocessor.js
robimy to:
effects = @querySelectorAll '.effect'
@composer.addPass effect.getPass() for effect in effects
Do znajdowania wszystkich efektów zagnieżdżonych jako elementy HTML w postprocessorze w kolejności, w jakiej zostały określone, używamy funkcji HTML i elementu JavaScript querySelectorAll
. Następnie przeprowadzamy je iterację i dodajemy do narzędzia kompozycyjnego.
Teraz załóżmy, że chcemy usunąć efekt głębi ostrości (DOF). zmienić kolejność kwitnienia kwiatów i efekty winietowe. Wystarczy, że zmodyfikujemy może to być np. definicja podmiotu przetwarzającego dane osobowe, np.:
<dom-module id="sw-experience-postprocessor">
<!-- Template-->
<template>
<sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
<sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
</template>
<!-- Polymer element script-->
<script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>
i scena będzie działać bez zmiany ani jednej linii kodu.
pętla renderowania i pętla aktualizacji w Polymerze
Dzięki Polymer można także elegancko podejść do renderowania i aktualizacji silnika.
Utworzyliśmy element timer
, który korzysta z metody requestAnimationFrame
i oblicza
takie jak bieżący czas (t
) i czas delta, czyli czas, który upłynął od
ostatnia klatka (dt
):
Polymer
is: 'sw-timer'
properties:
t:
type: Number
value: 0
readOnly: true
notify: true
dt:
type: Number
value: 0
readOnly: true
notify: true
_isRunning: false
_lastFrameTime: 0
ready: ->
@_isRunning = true
@_update()
_update: ->
if !@_isRunning then return
requestAnimationFrame => @_update()
currentTime = @_getCurrentTime()
@_setT currentTime
@_setDt currentTime - @_lastFrameTime
@_lastFrameTime = @_getCurrentTime()
_getCurrentTime: ->
if window.performance then performance.now() else new Date().getTime()
Następnie używamy wiązania danych, by powiązać właściwości t
i dt
z
wyszukiwarka (experience.jade
):
sw-timer(
t='{ % templatetag openvariable % }t}}',
dt='{ % templatetag openvariable % }dt}}'
)
sw-experience-engine(
t='[t]',
dt='[dt]'
)
Obserwujemy zmiany wartości t
i dt
w silniku i każdoraz, gdy się zmieniają, wywołujemy funkcję _update
:
Polymer
is: 'sw-experience-engine'
properties:
t:
type: Number
dt:
type: Number
observers: [
'_update(t)'
]
_update: (t) ->
dt = @dt
@_physics.update dt, t
@_renderer.render dt, t
Jeśli jednak zależy Ci na FPS, warto usunąć dane Polymer powiązanie w pętli renderowania w celu zaoszczędzenia kilku milisekund wymaganych do powiadomienia o zmianach. Wprowadziliśmy takie obserwacje:
sw-timer.coffee
:
addUpdateListener: (listener) ->
if @_updateListeners.indexOf(listener) == -1
@_updateListeners.push listener
return
removeUpdateListener: (listener) ->
index = @_updateListeners.indexOf listener
if index != -1
@_updateListeners.splice index, 1
return
_update: ->
# ...
for listener in @_updateListeners
listener @dt, @t
# ...
Funkcja addUpdateListener
przyjmuje wywołanie zwrotne i zapisuje je w swoim
tablicy wywołań zwrotnych. Następnie w pętli aktualizacji powtarzamy każde wywołanie zwrotne.
uruchamiamy je bezpośrednio za pomocą argumentów dt
i t
, omijając wiązanie danych lub
uruchamiania zdarzeń. Gdy wywołanie zwrotne nie jest już aktywne, dodaliśmy
Funkcja removeUpdateListener
, która umożliwia usunięcie wcześniej dodanego wywołania zwrotnego.
Miecz świetlny w THREE.js
THREE.js ogranicza szczegółowość technologii WebGL i pozwala nam skupić się dotyczącą problemu. Naszym problemem jest walka z szturmowcami i potrzebujemy broni. Zbudujmy więc miecz świetlny.
Świecące ostrze odróżnia miecz świetlny od dwuręczna broń. Składa się głównie z dwóch części: belki i szlaku co jest widoczne przy jej przesuwaniu. Jest to jasny cylinder z dynamicznym śladem, który podąża za ruchami gracza.
Ostrze
Ostrze składa się z 2 podłoży. Wewnętrzna i zewnętrzna. Obie sieci są siatkami THREE.js zawierającymi odpowiednie materiały.
Wewnętrzne ostrze
Wewnętrzną osłonę użyliśmy niestandardowego materiału z dostosowanym cieniowaniem. Śr wybierz linię utworzoną przez dwa punkty i rzutuj linię między tymi dwoma punktami punkty w samolocie. To właśnie ten samolot kontrolujesz, i grają na komórce. Daje to poczucie głębi i orientacji do miecza.
Aby stworzyć wrażenie świecącego się przedmiotu, patrzymy na ortogonalna odległość dowolnego punktu na płaszczyźnie od głównego łączący dwa punkty A i B, jak poniżej. Im bliżej punktu tym jaśniejszy jest obraz.
Poniżej źródło pokazuje, jak obliczamy vFactor
, aby kontrolować intensywność w shaderze wierzchołka, a następnie używać go do mieszania ze sceną w shaderze fragmentu.
THREE.LaserShader = {
uniforms: {
"uPointA": {type: "v3", value: new THREE.Vector3(0, -1, 0)},
"uPointB": {type: "v3", value: new THREE.Vector3(0, 1, 0)},
"uColor": {type: "c", value: new THREE.Color(1, 0, 0)},
"uMultiplier": {type: "f", value: 3.0},
"uCoreColor": {type: "c", value: new THREE.Color(1, 1, 1)},
"uCoreOpacity": {type: "f", value: 0.8},
"uLowerBound": {type: "f", value: 0.4},
"uUpperBound": {type: "f", value: 0.8},
"uTransitionPower": {type: "f", value: 2},
"uNearPlaneValue": {type: "f", value: -0.01}
},
vertexShader: [
"uniform vec3 uPointA;",
"uniform vec3 uPointB;",
"uniform float uMultiplier;",
"uniform float uNearPlaneValue;",
"varying float vFactor;",
"float getDistanceFromAB(vec2 a, vec2 b, vec2 p) {",
"vec2 l = b - a;",
"float l2 = dot( l, l );",
"float t = dot( p - a, l ) / l2;",
"if( t < 0.0 ) return distance( p, a );",
"if( t > 1.0 ) return distance( p, b );",
"vec2 projection = a + (l * t);",
"return distance( p, projection );",
"}",
"vec3 getIntersection(vec4 a, vec4 b) {",
"vec3 p = a.xyz;",
"vec3 q = b.xyz;",
"vec3 v = normalize( q - p );",
"float t = ( uNearPlaneValue - p.z ) / v.z;",
"return p + (v * t);",
"}",
"void main() {",
"vec4 a = modelViewMatrix * vec4(uPointA, 1.0);",
"vec4 b = modelViewMatrix * vec4(uPointB, 1.0);",
"if(a.z > uNearPlaneValue) a.xyz = getIntersection(a, b);",
"if(b.z > uNearPlaneValue) b.xyz = getIntersection(a, b);",
"a = projectionMatrix * a; a /= a.w;",
"b = projectionMatrix * b; b /= b.w;",
"vec4 p = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
"gl_Position = p;",
"p /= p.w;",
"float d = getDistanceFromAB(a.xy, b.xy, p.xy) * gl_Position.z;",
"vFactor = 1.0 - clamp(uMultiplier * d, 0.0, 1.0);",
"}"
].join( "\n" ),
fragmentShader: [
"uniform vec3 uColor;",
"uniform vec3 uCoreColor;",
"uniform float uCoreOpacity;",
"uniform float uLowerBound;",
"uniform float uUpperBound;",
"uniform float uTransitionPower;",
"varying float vFactor;",
"void main() {",
"vec4 col = vec4(uColor, vFactor);",
"float factor = smoothstep(uLowerBound, uUpperBound, vFactor);",
"factor = pow(factor, uTransitionPower);",
"vec4 coreCol = vec4(uCoreColor, uCoreOpacity);",
"vec4 finalCol = mix(col, coreCol, factor);",
"gl_FragColor = finalCol;",
"}"
].join( "\n" )
};
Poświata zewnętrznego ostrza
Aby uzyskać efekt poświaty, renderujemy oddzielny bufor renderowania i wykorzystujemy efekt bloom w postprocessingu, a następnie łączymy go z ostatecznym obrazem. Na grafice poniżej widać 3 różne regiony, potrzebne, jeśli chcecie mieć porządną szafę. Chodzi o białe jądro, niebieską poświatę w środku i poświatę na zewnątrz.
Szlak mieczy świetlnych
Ślad miecza świetlnego jest kluczowy dla pełnego efektu, tak jak w pierwotnym mieczu w serii Gwiezdne Wojny. Wyruszyliśmy na szlak z wielbiciele stworzonych trójkątów dynamicznie w zależności od ruchu miecza świetlnego. Ci fani są są przekazywane do późniejszego ulepszenia wizualnego. Aby utworzyć mamy geometrię wentylatora. i bieżące przekształcenie generujemy w siatce nowy trójkąt, z ogona po określonej długości.
Gdy mamy już siatkę, przypisujemy do niej prosty materiał i przekazujemy postprocesorem w celu uzyskania płynnego efektu. Używamy tego samego efektu kwitnienia zastosowaliśmy poświatę zewnętrznego ostrza i pozyskaliśmy gładki ślad, jak widać:
Świecące wokół szlaku
Aby dokończyć ostatni element, musieliśmy zmierzyć się ze światłem które można stworzyć na wiele sposobów. Nasze rozwiązanie nie podawać tutaj szczegółów. Ze względu na wydajność trzeba było utworzyć niestandardowy program do cieniowania dla tego bufora, który tworzy gładką krawędź wokół bufor renderowania. Następnie łączymy dane wyjściowe w ostatecznym renderowaniu, zobacz poświatę, która otacza szlak:
Podsumowanie
Polymer to zaawansowana biblioteka i koncepcja (tak samo jak WebKomponent ogólne). To, co na nim zrobisz, zależy tylko od Ciebie. Może to być wszystko, od prostego przycisku w interfejsie do pełnowymiarowej aplikacji WebGL. W poprzednich rozdziałach omówiliśmy kilka porad i wskazówek, jak efektywnie korzystać z technologii Polymer, w środowisku produkcyjnym oraz jak tworzyć bardziej złożone moduły, które również zapewniają cóż. Pokazaliśmy również, jak stworzyć ładny miecz świetlny w WebGL. Więc jeśli to wszystko połączysz, pamiętaj o wulkanizacji pierwiastków polimerowych przed wdrożeniem na serwerze produkcyjnym oraz Jeśli chcesz zachować zgodność z zasadami CSP, możesz liczyć na siebie.