Product Page
Introduction
This sample showcases how to build a product page in AMP HTML.
Metadata
The page indexing requires schema.org markup for one of the following types: Type, AggregateRating, Offers. Learn more.
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@type": "Product",
"name": "Apple",
"image": "https://amp.dev/static/samples/img/golden_apple1_1024x682.jpg",
"description": "Lorem ipsum",
"mpn": "925872",
"brand": {
"@type": "Fruit",
"name": "Apple"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.4",
"reviewCount": "88"
},
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "1.99",
"priceValidUntil": "2020-11-05",
"itemCondition": "http://schema.org/UsedCondition",
"availability": "http://schema.org/InStock",
"seller": {
"@type": "Retail",
"name": "AMP by Example"
}
}
}
</script>
Navigation
Use the amp-sidebar component to give customers the chance to quickly jump to any of your product categories.
<amp-sidebar id="drawermenu" layout="nodisplay">
<a href="/" class="caps text-decoration-none block p1">Products</a>
<hr/>
<a class="caps text-decoration-none block p1" href="/samples_templates/product_browse_page/preview/">Fruit</a>
<a class="caps text-decoration-none block p1" href="/samples_templates/product_browse_page/preview/">Vegetable</a>
<a class="caps text-decoration-none block p1" href="/samples_templates/product_browse_page/preview/">More</a>
</amp-sidebar>
Header with Search
AMP supports forms, which means you can directly integrate a product search into your AMPs. Try searching for "Apple".
You can use CSS3 animations to enrich your page. The expanding text field when the search box is focused is implemented with CSS only.
<div class="header">
<a id="sample-menu" on="tap:drawermenu.toggle">
<amp-img srcset="/static/samples/img/ic_menu_white_1x_web_24dp.png 1x, /static/samples/img/ic_menu_white_2x_web_24dp.png 2x"
width="24" height="24"
alt="navigation"></amp-img>
</a>
<form method="GET" action="/documentation/examples/api/search" target="_top">
<input name="search" type="search" placeholder="Search">
<a id="sample-logo" href="/">Product</a>
<input type="submit" value="">
</form>
</div>
Social Sharing
The Social Share extension provides a common interface for share buttons. Learn more about amp-social-share
here.
<div class="m1">
<amp-social-share type="twitter"
width="30"
height="22"></amp-social-share>
<amp-social-share type="facebook"
width="30"
height="22"
data-attribution="254325784911610"></amp-social-share>
<amp-social-share type="email"
width="30"
height="22"></amp-social-share>
<amp-social-share type="pinterest"
width="33"
height="22"></amp-social-share>
</div>
Video
AMP supports a wide range of video platforms. Here we are using amp-youtube
to show a product video. You can find an overview of all supported video platforms here.
In this sample we don't embed the video directly into the page, but instead show it in a lightbox using the amp-lightbox
component. Learn more about amp-lightbox
here.
<button class="ampstart-btn caps m1 mb3" on="tap:watch-video-lightbox" >
Show Video
</button>
Slides preview Gallery
On desktop we've added a film strip like slide preview. Each product colour has its own image gallery and slide preview.
We use amp-bind to update the page selecting the gallery and image preview
corresponding to the selected colour. We realized the color selection by using amp-selector
. Find more the doc and info in The Product Page section below.
The green colour is the default color for the gallery when the page loads.
Clicking on a preview image will reveal the image in the gallery via the tap action on="tap:AMP.setState({product: {selectedSlideForGreen: 0}})"
.
See below for more informations on how we use amp-bind
.
<div class="product-gallery">
<ul [hidden]="product.selectedColor != 'green'">
<li>
<amp-img on="tap:AMP.setState({product: {selectedSlideForGreen: 0}})"
src="/static/samples/img/green_apple_1_60x40.jpg"
width="60" height="40"
class="selected"
[class]="product.selectedSlideForGreen == 0 ? 'selected' : '' "
tabindex="0" role="button">
</amp-img>
</li>
<li>
<amp-img on="tap:AMP.setState({product: {selectedSlideForGreen: 1}})"
src="/static/samples/img/green_apple_2_60x40.jpg"
width="60" height="40"
[class]="product.selectedSlideForGreen == 1 ? 'selected' : '' "
tabindex="1" role="button">
</amp-img>
</li>
</ul>
<ul hidden [hidden]="product.selectedColor != 'golden'">
<li>
<amp-img on="tap:AMP.setState({product: {selectedSlideForGolden: 0}})"
src="/static/samples/img/product1_alt1_60x40.jpg"
width="60" height="40"
class="selected fadeIn"
[class]="product.selectedSlideForGolden == 0 ? 'selected fadeIn' : '' "
tabindex="0" role="button">
</amp-img>
</li>
</ul>
<ul hidden [hidden]="product.selectedColor != 'red'">
<li>
<amp-img on="tap:AMP.setState({ product: {selectedSlideForRed : 0}})"
src="/static/samples/static/samples/img/red_apple_1_60x40.jpg"
width="60" height="40"
class="selected fadeIn"
[class]="product.selectedSlideForRed == 0 ? 'selected fadeIn' : '' "
tabindex="0" role="button">
</amp-img>
</li>
<li>
<amp-img on="tap:AMP.setState({ product: {selectedSlideForRed : 1}})"
src="/static/samples/static/samples/img/red_apple_2_60x46.jpg"
width="60" height="40"
[class]="product.selectedSlideForRed == 1 ? 'selected' : '' "
tabindex="1" role="button">
</amp-img>
</li>
</ul>
</div>
Image Gallery
The amp-carousel
component works very well for product image galleries. Learn how the amp-carousel
component works here.
Notice how we are binding to the slide
and class
by using to the values of the variables selectedSlideForGreen
and selectedColor
.
<div class="product-gallery">
<amp-carousel id="green-apple-carousel"
width="1024" height="682"
layout="responsive"
type="slides"
[slide]="product.selectedSlideForGreen"
on="slideChange: AMP.setState({product: {selectedSlideForGreen: event.index}})"
class="fadeIn"
[hidden]="product.selectedColor != 'green'">
<amp-img src="/static/samples/img/green_apple_1_1024x682.jpg"
width="1024" height="682"
layout="responsive" on="tap:gallery-lightbox"
role="button" tabindex="0">
</amp-img>
<amp-img src="/static/samples/img/green_apple_2_1024x685.jpg"
width="1024" height="682"
layout="responsive" on="tap:gallery-lightbox"
role="button" tabindex="0">
</amp-img>
</amp-carousel>
<amp-carousel id="golden-apple-carousel"
width="1024" height="682"
layout="responsive"
type="slides"
[slide]="product.selectedSlideForGolden"
on="slideChange: AMP.setState({product: {selectedSlideForGolden: event.index}})"
hidden
class="fadeIn"
[hidden]="product.selectedColor != 'golden'">
<amp-img src="/static/samples/img/golden_apple1_1024x682.jpg"
width="1024" height="682"
layout="responsive" on="tap:gallery-lightbox"
role="button" tabindex="0">
</amp-img>
</amp-carousel>
<amp-carousel id="red-apple-carousel"
width="1024" height="682"
layout="responsive"
type="slides"
[slide]="product.selectedSlideForRed"
on="slideChange: AMP.setState({product: {selectedSlideForRed: event.index}})"
hidden
class="fadeIn"
[hidden]="product.selectedColor != 'red'">
<amp-img src="/static/samples/img/red_apple_1_1024x682.jpg"
width="1024" height="682"
layout="responsive" on="tap:gallery-lightbox"
role="button" tabindex="0">
</amp-img>
<amp-img src="/static/samples/img/red_apple_2_1024x793.jpg"
width="1024" height="682"
layout="responsive" on="tap:gallery-lightbox"
role="button" tabindex="0">
</amp-img>
</amp-carousel>
</div>
Full screen image
The amp-image-lightbox
component allows the user to expand an image to fill the viewport.
By clicking on each image from the product gallery, the user can see the product image by using the full screen.
Learn more here
<amp-image-lightbox id="gallery-lightbox" layout="nodisplay">
<div on="tap:gallery-lightbox.close" role="button"
tabindex="0">
<button class="ampstart-btn caps m2 close-gallery-button" on="tap:gallery-lightbox.close"
role="button" tabindex="0">
Close
</button>
</div>
</amp-image-lightbox>
Product Configuration
We use the amp-state
component (part of amp-bind)
to configure the product price depending on the color and the size. We also configure the defaultSize
for each product when you switch between colours and the size you previously selected is not available for the new color
<amp-state id="product">
<script type="application/json">
{
"selectedColor": "green",
"selectedSize": "S",
"selectedSlideForRed": 0,
"selectedSlideForGolden": 0,
"selectedSlideForGreen": 0,
"moreItemsPageIndex": 0,
"hasMorePages": true,
"green": {
"id": "1",
"sizes": {
"S": "$5.99",
"M": "$5.99",
"L": "unavailable"
},
"defaultSize": "S"
},
"golden": {
"id": "2",
"sizes": {
"S": "$9.99",
"M": "unavailable",
"L": "$9.99"
},
"defaultSize": "L"
},
"red": {
"id": "3",
"sizes": {
"S": "$7.99",
"M": "$7.99",
"L": "$7.99"
},
"defaultSize": "M"
}
}
</script>
</amp-state>
Product price
We use amp-bind to update the price of the product
depending on the selected colour.
We bind the text
attribute to the value of expression product[product.selectedColor].sizes[product.selectedSize]
. product
is the id amp-state
json described in the Product Configuration section.
Notice how we set $1.99 value as the default for the price: expressions will not be evaluated at the page load.
We use amp-selector
together with amp-bind
to update the page for showing the price, the size availability, the gallery and preview of images.
Every time you select a color, we call AMP.setState
with {selectedColor: event.targetOption}
. This is triggering
all the attributes and elements binded to selectedColor
to update: the price (binded with the attribute [text]
), the gallery, the image preview and the size selector (all binded with [class]
).
Price: $5.99
<p class="price-description">Price:
<span [text]="product[product.selectedColor].sizes[product.selectedSize]">$5.99</span>
</p>
Product page
The add-to-cart action action is implemented using amp-form. In our sample we use the amp-selector for selecting different product properties.
Pressing the ADD TO CART
button adds the product to a shopping cart page using the properties you have selected. Notice that the button URL contains the query clientId={{ClientId}}
.
If an item is successfully added to the shopping cart, we redirect the user to the shopping cart page.
The redirect target is configured via a header in the server's form response (redirecting after submission):
access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin,AMP-Redirect-To amp-redirect-to:http://ampbyexample.com/shopping_cart/?clientid=amp-123456789
We use the CLIENT_ID
variable to identify the user, this enables storing a shopping cart between repeated visits of an AMP page (either via the AMP Cache or the original host). This variable can be used inside an amp-form by declaring an hidden input value with default-value="CLIENT_ID(cart)
. Read more about variable substitution here.
<form id="order" method="POST"
action-xhr="/documentation/examples/e-commerce/shopping_cart/add-to-cart"
target="_top" class="flex flex-wrap m1">
<div class="items-center flex">
<label for="color">Color:</label>
<amp-selector name="color"
layout="container"
[selected]="product.selectedColor"
on="select:AMP.setState({
product: {
selectedColor: event.targetOption,
selectedSlideForRed: 0,
selectedSlideForGreen: 0,
selectedSlideForYellow: 0,
selectedSize: product[event.targetOption].sizes[product.selectedSize] != 'unavailable' ? product.selectedSize : product[event.targetOption].defaultSize
}
})">
<ul class="p0 m1">
<li>
<div option="green" selected class="square green"></div>
</li>
<li>
<div option="golden" class="square golden"></div>
</li>
<li>
<div option="red" class="square red"></div>
</li>
</ul>
</amp-selector>
</div>
<div class="items-center flex">
<label for="quantity">Quantity:</label>
<amp-selector name="quantity" layout="container">
<ul class="p0 m1">
<li option="1" selected>1</li>
<li option="2">2</li>
<li option="3">3</li>
</ul>
</amp-selector>
</div>
<div class="items-center flex">
<label for="size">Size:</label>
<amp-selector name="size" layout="container"
on="select:AMP.setState({ product: {selectedSize: event.targetOption}})"
[selected]="(product[product.selectedColor].sizes[product.selectedSize] != 'unavailable')
? product.selectedSize
: product[product.selectedColor].defaultSize">
<ul class="p0 m1">
<li option="S" class=""
[class]="(product[product.selectedColor].sizes['S'] != 'unavailable')
? ''
: 'unavailable'">S</li>
<li option="M" class="" selected
[class]="(product[product.selectedColor].sizes['M'] != 'unavailable')
? ''
: 'unavailable'">M</li>
<li option="L" class="unavailable"
[class]="(product[product.selectedColor].sizes['L'] != 'unavailable')
? ''
: 'unavailable'">L</li>
</ul>
</amp-selector>
</div>
<div class="items-center flex my1">
<input type="submit" class="ampstart-btn caps" name="add-to-cart" value="add to cart">
</div>
<input type="hidden" name="name" value="Apple">
<input type="hidden" name="price" value="$1.99" [value]="product[product.selectedColor].sizes[product.selectedSize]">
<input type="hidden" name="id" value="1" [value]="product[product.selectedColor].id">
<input name="clientId" type="hidden" value="CLIENT_ID(cart)" data-amp-replace="CLIENT_ID">
<div submit-error>
<template type="amp-mustache">
Error! Looks like something went wrong with your shopping cart, please try to add an item again. {{error}}
</template>
</div>
</form>
Tab panels
Use amp-selector
styled as tab panels to add additional data about your product.
Learn how to implement tabs in AMP here.
<amp-selector role="tablist"
layout="container"
class="ampTabContainer ampstart-headerbar-nav" keyboard-select-mode="select">
<div role="tab"
class="tabButton h4 ampstart-nav-item"
selected
option="a">ABOUT</div>
<div role="tabpanel"
class="tabContent p1 p">Fruit is rich in vitamins and minerals. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div>
<div role="tab"
class="tabButton h4 ampstart-nav-item"
option="b">SPECS</div>
<div role="tabpanel"
class="tabContent p1 p">Always wash fruit before eating. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
<div role="tab"
class="tabButton h4 ampstart-nav-item"
option="c">SIZE</div>
<div role="tabpanel"
class="tabContent p1 p">Size may vary. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
</amp-selector>
Dynamic Related Product Lists
With AMP, you can easily pull in different latest offers or highlights without changing the page. To do so, just use amp-list
to fire a CORS request to a JSON endpoint which supplies the list of related products. These are populated into an amp-mustache template on the client.
Learn more about amp-list
here.
<amp-list class="items m1"
width="auto"
height="145"
layout="fixed-height"
src="/static/samples/json/related_products.json"
[src]="myState.items"
binding="no"
id="show-more-list">
<template type="amp-mustache">
<a class="text-decoration-none p1"
href="/documentation/examples/e-commerce/product_page/preview/">
<amp-img width="70.31"
height="46.8"
layout="fixed"
alt="{{name}}"
src="{{img}}"></amp-img>
<p class="name">{{name}}</p>
<p class="star">{{{stars}}}</p>
<p class="price">${{price}}</p>
</a>
</template>
</amp-list>
While infinite scrolling is not possible at the moment in AMP, you can use amp-bind
to dynamically change the
src of amp-list
and add more items to the page.
We bind the src
attribute to the id of an amp-state
component so that the amp-list
will use the items
from that component as a src
. The amp-state
initial value is set by setting the src
value
to the same endpoint used by amp-list
. We append items to the amp-state
everytime the user click on the Show more button, see below for more info.
<amp-state id="myState" src="/static/samples/json/related_products.json">
</amp-state>
You can show or hide a Show more button by using amp-form
and the submit-success
event: notice
how we set the hasMorePages
variable to false based on the server response.
<form method="GET"
action="/documentation/examples/api/json/more_related_products_page"
action-xhr="/documentation/examples/api/json/more_related_products_page"
target="_top"
on="submit-success: AMP.setState({
myState: { items: myState.items.concat(event.response.items)},
product: {moreItemsPageIndex: product.moreItemsPageIndex + 1,
hasMorePages: event.response.hasMorePages}
});">
<input type="hidden" name="moreItemsPageIndex" value="0" [value]="product.moreItemsPageIndex">
<input type="submit" value="Show more"
class="ampstart-btn caps m1 mb3"
[hidden] = "!product.hasMorePages">
</form>
User Analytics
Analytics must be configured in the body. Here we use Google Analytics to track pageviews.
<amp-analytics type="googleanalytics">
<script type="application/json">
{
"vars": {
"account": "UA-73836974-1"
},
"triggers": {
"default pageview": {
"on": "visible",
"request": "pageview",
"vars": {
"title": "Product"
}
}
}
}
</script>
</amp-analytics>
이 페이지의 설명만으로 궁금한 점이 모두 해결되지 않는다면 다른 AMP 사용자에게 문의하여 구체적인 활용 사례를 논의해 보세요.
Stack Overflow로 이동 설명이 부족한 기능을 발견하셨나요?AMP 프로젝트는 여러분의 참여와 기여를 적극 환영합니다! 오픈 소스 커뮤니티를 통해 지속적으로 활동해 주셔도 좋지만 관심 있는 주제에 한 번만 기여하셔도 큰 도움이 됩니다.
GitHub에서 샘플 수정하기-
Written by @aghassemi