JavaScriptでMac OSXのDockのように拡大縮小するアイコン
2019-03-25
題名のとおりなんですけど、マウスホバーでアイコンが拡大表示される、Mac OSXのDockのような動きをするメニューをJavaScriptで書きました。
例によってライブラリとかjQueryを使用していないので、掲載しているコードだけで動きます。
2016/6/13追記:スムーズさを出すためにフレームレートを変更したのと若干のコード変更を行いました。
2019/3/25追記:SVGアイコンをutf-8の生コードからbase64エンコードデータに変更しました。
このDockメニューはActionScript(Flash)でやってから何度も書いているのですが、HTML5やCSS3、SVGのおかげでだいぶ簡単に実現することができるようになりました。
デモ
下のSNS系のアイコンの上にマウスを乗っけてみてください。ちゃんとそれっぽく動くでしょう?!
Mac OSXが出てから15年以上にわたって変わらないGUIとして見た場合のDockは、やはり優れたGUIなんでしょうね。それをWebで実現したからといって使いどころは難しいですけど。。
DockのアイコンをSVGで描いていて、cssの::before擬似要素のcontent属性に設定しています。以前は、utf-8の生のSVGでcss内に記述しても問題ありませんでしたが、いつからか表示されなくなってましたので、修正してbase64エンコードしたSVGに変更しました。
クリックしても単なるダミーのURLを貼っているだけなので何も起きません。
これだけのことを実行するのに、JavaScriptの部分だけなら、Minify + gzipでたったの600バイト程度です。HTMLコードやCSS、8個のSVGアイコンも含めても3,500バイト程度ですよ。
小さいコードで書くことは、ダウンロード時間を短くするだけじゃなくて、今回のコードのように50/s程度のフレームレートで多数の計算やDOM変更を伴う場合でもスムーズに動く要因ともなります。
それでは、上記のデモを実現するコードを紹介します。
コード(HTML + CSS + JavaScript + アイコンSVG)
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Dock</title>
<style>
#sns {
position: relative;
margin: 0 auto;
display: flex;
}
/* for touch devices and javascript off user */
#sns li {
flex: 1 1 auto;
list-style: none;
}
#sns a {
display: block;
text-align: center;
}
#sns a::before {
display: block;
margin: 0 auto;
}
.dock li {
display: block;
position: absolute;
bottom: 0;
transform-origin: 50% 100%;
}
.dock li.hide {
opacity: 0;
}
.dock a {
display: block;
width: 30px;
height: 30px;
padding: 4px;
margin: 0 1px;
border-radius: 4px;
}
.dock span {
display: none;
}
.dock li:hover span {
position: absolute;
display: block;
margin: 0 0 3px;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
text-align: center;
font-size: 10px;
border-radius: 1em;
padding: .1em 1em;
color: #fff;
background: #666;
}
.dock li:hover span::before {
content:'';
position:absolute;
display:block;
width:0;
bottom:-3px;
left:50%;
margin-left: -4px;
border-style:solid;
border-color:#666 transparent;
border-width: 4px 4px 0;
}
.facebook {
background: #315096;
}
.facebook::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00xOCAzMmgtNlYxNkg4di01LjVsNC0uMDJWNy4yNEMxMiAyLjc0IDEzLjIgMCAxOC41IDBoNC40MnY1LjVoLTIuNzVDMTguMSA1LjUgMTggNi4zIDE4IDcuNzN2Mi43Nmg0Ljk1TDIyLjM4IDE2SDE4djE2eicvPjwvc3ZnPg==");
}
.twitter {
background: #55acee;
}
.twitter::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00zMiA2LjA4Yy0xLjE4LjUyLTIuNDQuODctMy43NyAxLjAzIDEuMzUtLjggMi40LTIuMSAyLjktMy42Mi0xLjI4Ljc1LTIuNyAxLjMtNC4xOCAxLjYtMS4yLTEuMy0yLjktMi4wOC00LjgtMi4wOC0zLjYyIDAtNi41NiAyLjk0LTYuNTYgNi41NiAwIC41Mi4wNSAxLjAyLjE2IDEuNS01LjQ2LS4yNy0xMC4zLTIuOS0xMy41My02Ljg2LS41Ny45Ny0uOSAyLjEtLjkgMy4zIDAgMi4yOCAxLjE3IDQuMyAyLjkzIDUuNDctMS4wOC0uMDQtMi4xLS4zMy0yLjk3LS44My0uMDIuMDMtLjAyLjA2LS4wMi4xIDAgMy4xNyAyLjI3IDUuODIgNS4yNyA2LjQyLS41NS4xNS0xLjEzLjIzLTEuNzMuMjMtLjQyIDAtLjgzLS4wNS0xLjIzLS4xMi44MiAyLjYgMy4yNSA0LjUgNi4xMiA0LjU2LTIuMjUgMS43Ni01LjA4IDIuOC04LjE2IDIuOC0uNTMgMC0xLjA1LS4wMy0xLjU2LS4xQzIuOSAyNy45MyA2LjM1IDI5IDEwLjA2IDI5YzEyLjA4IDAgMTguNjgtMTAgMTguNjgtMTguNjggMC0uMjggMC0uNTYtLjAyLS44NSAxLjMtLjkyIDIuNC0yLjA4IDMuMjgtMy40eicvPjwvc3ZnPg==");
}
.hateb {
background: #008fde;
}
.hateb::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTIzLjg3IDI0LjM1YzAgMS43NCAxLjQgMy4xNSAzLjE1IDMuMTVzMy4xNi0xLjQgMy4xNi0zLjE1YzAtMS43NC0xLjQyLTMuMTYtMy4xNi0zLjE2cy0zLjE1IDEuNC0zLjE1IDMuMTV6bS4yOC0yMC42Mmg1Ljc1djE1LjdoLTUuNzV6TTE1LjQgMTQuM3MzLjk4LS4yNiAzLjk4LTUuMDJjMC01LjUtNC45OC01LjU0LTcuODItNS41NEgxLjgydjIzLjc4aDkuNjVjNy44NiAwIDkuMi00LjMgOS4yLTcuMDVzLTEuMzQtNS4zNC01LjI2LTYuMTh6TTcuODggOC4yaDIuNjdjLjUgMCAyLjY3LjIgMi42NyAyLjI4IDAgMi40NC0xLjg4IDIuMzYtMy4xMyAyLjM2SDcuODdWOC4yMnpNMTEgMjIuNDZINy45di01LjIyaDMuMnMzLjIuMzggMy4yIDIuNi0xLjggMi42Mi0zLjI3IDIuNjJ6Jy8+PC9zdmc+");
}
.pocket {
background: #ee4056;
}
.pocket::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00yNy41IDMuMTdINC4zM2MtMS41OCAwLTIuODYgMS4yNy0yLjg2IDIuODV2OC40NWMwIDcuOTggNi40NyAxNC40NSAxNC40NSAxNC40NSA3LjkgMCAxNC4zMi02LjM0IDE0LjQ2LTE0LjJ2LTguN2MwLTEuNTgtMS4yNy0yLjg1LTIuODUtMi44NXpNMjQgMTRsLTYuNDcgNi40N2MtLjQyLjQ0LTEuMDIuNjItMS42LjU1LS41Ni4wNy0xLjE1LS4xLTEuNi0uNTVMNy44NSAxNGMtLjc1LS43NS0uNzUtMS45OCAwLTIuNzRzMS45OC0uNzUgMi43NCAwbDUuMzMgNS4zNCA1LjM1LTUuMzRjLjc1LS43NSAxLjk4LS43NSAyLjczIDBzLjc2IDIgMCAyLjc0eicvPjwvc3ZnPg==");
}
.feedly {
background: #6cc655;
}
.feedly::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00zMC41NiAxNi42N2wtMTMuMy0xMy4zYy0uNy0uNy0xLjgyLS43LTIuNTIgMGwtMTMuMyAxMy4zYy0uNy43LS43IDEuODIgMCAyLjUybDkuOTYgOS45NWg5LjJsOS45Ni05Ljk2Yy43LS43LjctMS44MyAwLTIuNTN6bS0yMi4xMyAyLjhsLTEuNDgtMS41Yy0uMS0uMS0uMS0uMjYgMC0uMzdsOC43OC04Ljc4Yy4xLS4xLjI3LS4xLjM3IDBsMS45OCAxLjk4Yy4xLjEuMS4yNyAwIC4zN2wtOC4zIDguM0g4LjQ0em05LjczIDUuNWwtMS40OCAxLjQ3aC0xLjM2bC0xLjQ4LTEuNDhjLS4xLS4xLS4xLS4yNyAwLS4zN2wxLjk3LTJjLjEyLS4xLjI4LS4xLjQgMGwxLjk2IDJjLjEuMS4xLjI2IDAgLjM2ek0xOC4yIDE4bC00Ljk0IDQuOTJIMTEuOWwtMS41LTEuNDhjLS4xLS4xLS4xLS4yNyAwLS4zN2w1LjQ0LTUuNDJjLjEtLjEuMjctLjEuMzcgMGwyIDEuOThjLjEuMS4xLjI3IDAgLjM3eicvPjwvc3ZnPg==");
}
.googlep {
background: #dd4b39;
}
.googlep::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00xNy40NyAySDkuMUM1LjM0IDIgMS44IDQuODQgMS44IDguMTRjMCAzLjM2IDIuNTcgNi4wOCA2LjQgNi4wOC4yNiAwIC41IDAgLjc3LS4wMi0uMjUuNDctLjQzIDEtLjQzIDEuNTYgMCAuOTQuNSAxLjcgMS4xNCAyLjMtLjQ4IDAtLjk0LjAzLTEuNDUuMDNDMy41OCAxOC4xIDAgMjEuMDQgMCAyNC4xYzAgMy4wMiAzLjkyIDQuOTIgOC41NyA0LjkyIDUuMyAwIDguMjMtMyA4LjIzLTYuMDQgMC0yLjQyLS43LTMuODctMi45My01LjQ0LS43NS0uNTMtMi4yLTEuODQtMi4yLTIuNiAwLS45LjI2LTEuMzQgMS42LTIuNCAxLjQtMS4wOCAyLjM3LTIuNiAyLjM3LTQuMzcgMC0yLjEtLjk0LTQuMTctMi43LTQuODVoMi42NkwxNy40NyAyem0tMi45MiAyMC40OGMuMDYuMjguMS41Ny4xLjg3IDAgMi40NC0xLjU4IDQuMzUtNi4xIDQuMzUtMy4yIDAtNS41My0yLjA0LTUuNTMtNC40OCAwLTIuNCAyLjg4LTQuNCA2LjEtNC4zNS43NCAwIDEuNDQuMTMgMi4wOC4zMyAxLjc0IDEuMiAzIDEuOSAzLjM1IDMuMjh6bS01LjE1LTkuMWMtMi4xNi0uMDgtNC4yLTIuNDMtNC41OC01LjI2czEuMDctNSAzLjIzLTQuOTNjMi4xNi4wNSA0LjIgMi4zMiA0LjU4IDUuMTZzLTEuMDcgNS4wNy0zLjIzIDV6TTI2IDhWMmgtMnY2aC02djJoNnY2aDJ2LTZoNlY4eicvPjwvc3ZnPg==");
}
.line {
background: #00c300;
}
.line::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00xNS45MyAxLjk1Yy03Ljk2IDAtMTQuNCA1LjI4LTE0LjQgMTEuOCAwIDUuODQgNS4yIDEwLjcgMTIuMDIgMTEuNjNoLjA4bC4yMy4wNGMuNzUuMSAxLjEuMjggMS4xIDEuMS0uMDIuOTItLjM4IDEuNi0uNiAyLjA0cy0uNjcgMi4yNSAxLjQgMS4xM2MxLjU4LS44OCA5LjM3LTQuNyAxMi44Ny0xMC4zNy45My0xLjQgMS41LTMgMS42Ny00LjY4di0uMTVsLjAzLS4yNXYtLjVjMC02LjUyLTYuNDQtMTEuOC0xNC40LTExLjh6TTkuOSAxNy4xNUg3LjEzYy0uNCAwLS43My0uMzMtLjczLS43M3YtNS41N2MwLS40LjMzLS43NC43NC0uNzRzLjczLjM0LjczLjc1djQuODNoMmMuNDIgMCAuNzUuMzMuNzUuNzNzLS4zMy43NS0uNzQuNzV6bTMuMTItLjczYzAgLjQtLjMzLjczLS43NC43M3MtLjczLS4zMy0uNzMtLjczdi01LjU3YzAtLjQuMzMtLjc0LjczLS43NHMuNzQuMzQuNzQuNzV2NS41NnptNi43LjVjMCAuMDItLjAyLjAzLS4wMy4wNGwtLjEuMDZjMCAuMDItLjAzLjAzLS4wNi4wNGwtLjA2LjAzYy0uMDIgMC0uMDUgMC0uMDguMDJoLS4wNWwtLjE1LjAzYy0uMDUgMC0uMSAwLS4xNC0uMDItLjAyIDAtLjA1IDAtLjA3LS4wMnMtLjA2IDAtLjA4IDBsLS4wNy0uMDVjLS4wMiAwLS4wNCAwLS4wNS0uMDItLjEtLjA3LS4xNy0uMTUtLjIzLS4yNEwxNS43MiAxM3YzLjRjMCAuNC0uMzMuNzMtLjczLjczcy0uNzQtLjMzLS43NC0uNzR2LTUuNTUtLjA0LS4xYy4wMi0uMDIuMDMtLjA1LjAzLS4wN3YtLjA2bC4wNi0uMXMwLS4wMi4wMi0uMDNjLjA1LS4wOC4xMi0uMTUuMi0uMi4wMiAwIC4wMy0uMDIuMDUtLjAzbC4wOC0uMDMuMDgtLjAyLjA3LS4wMi4xMy0uMDJIMTVjLjA1IDAgLjEuMDIuMTQuMDMuMDIgMCAuMDQgMCAuMDYuMDIuMDIgMCAuMDUgMCAuMDcuMDIuMDMgMCAuMDUuMDIuMDcuMDRsLjA2LjA0Yy4wNC4wMi4wOC4wNS4xLjEuMDUuMDMuMS4wOC4xMi4xM2wyLjg1IDMuNzd2LTMuNGMwLS40LjMzLS43My43My0uNzNzLjc0LjM0Ljc0Ljc1djUuNTZjMCAuMDYgMCAuMS0uMDIuMTZ2LjA1bC0uMDQuMWMwIC4wMi0uMDIuMDMtLjAzLjA1IDAgLjAzLS4wMi4wNS0uMDQuMDcgMCAuMDMtLjAzLjA2LS4wNS4wOGwtLjAzLjAzem00LjgyLTQuMDJjLjQgMCAuNzMuMzMuNzMuNzNzLS4zMy43My0uNzMuNzNoLTJ2MS4zMmgyYy40IDAgLjczLjMzLjczLjczcy0uMzMuNzUtLjczLjc1SDIxLjhjLS40MiAwLS43NC0uMzMtLjc0LS43NHYtNS41NWMwLS40LjMzLS43NC43My0uNzRoMi43NGMuNCAwIC43My4zNC43My43NXMtLjMzLjczLS43My43M2gtMnYxLjNoMnonLz48L3N2Zz4=");
}
.github {
background: #333;
}
.github::before {
content: url("data:image/svg+xml; base64, PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnICB2aWV3Qm94PScwIDAgMzIgMzInPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J00xNiAwQzcuMTYgMCAwIDcuMTYgMCAxNnM3LjE2IDE2IDE2IDE2IDE2LTcuMTYgMTYtMTZTMjQuODQgMCAxNiAwem05LjUgMjUuNWMtMS4yMyAxLjI0LTIuNjcgMi4yLTQuMjcgMi44OC0uNC4xOC0uODIuMzMtMS4yNC40NnYtMi40YzAtMS4yNi0uNDQtMi4yLTEuMy0yLjc4LjU0LS4wNiAxLjAzLS4xMyAxLjUtLjIycy45Mi0uMjMgMS40Mi0uNC45Ni0uNCAxLjM2LS42NC44LS41NiAxLjE2LS45NS42OC0uODQuOTMtMS4zMy40NS0xLjEuNi0xLjc4LjItMS40Ni4yLTIuM2MwLTEuNi0uNS0yLjk4LTEuNTctNC4xMi40OC0xLjI1LjQzLTIuNi0uMTUtNC4wN2wtLjQtLjA1Yy0uMjYtLjAzLS43NS4wOC0xLjQ1LjM0cy0xLjUuNy0yLjM3IDEuMjhjLTEuMjQtLjM0LTIuNTMtLjUtMy44Ni0uNS0xLjM0IDAtMi42Mi4xNi0zLjg0LjUtLjU2LS4zNy0xLjA4LS42OC0xLjU3LS45M3MtLjktLjQyLTEuMi0uNS0uNTYtLjE1LS44Mi0uMTctLjQyLS4wMy0uNS0uMDJMOCA3Ljg1Yy0uNiAxLjQ4LS42NCAyLjg0LS4xNiA0LjA4LTEuMDYgMS4xNC0xLjU4IDIuNS0xLjU4IDQuMTMgMCAuODMuMDcgMS42LjIyIDIuM3MuMzQgMS4yNy42IDEuNzcuNTUuOTQuOTIgMS4zMy43Ni43IDEuMTYuOTUuODUuNDUgMS4zNi42My45OC4zIDEuNDMuNC45Ni4xNyAxLjUuMjNjLS44Ni41OC0xLjI4IDEuNS0xLjI4IDIuNzh2Mi40NGMtLjQ4LS4xNC0uOTQtLjMtMS40LS41LTEuNi0uNjctMy4wNC0xLjY0LTQuMjctMi44OHMtMi4yLTIuNjctMi44OC00LjI3Yy0uNy0xLjY2LTEuMDYtMy40Mi0xLjA2LTUuMjNzLjM2LTMuNTggMS4wNi01LjIzQzQuMyA5LjE3IDUuMjYgNy43MyA2LjUgNi41czIuNjctMi4yIDQuMjctMi44OGMxLjY2LS43IDMuNDItMS4wNiA1LjIzLTEuMDZzMy41OC4zNiA1LjIzIDEuMDZjMS42LjY3IDMuMDQgMS42NCA0LjI3IDIuODhzMi4yIDIuNjcgMi44OCA0LjI3Yy43IDEuNjUgMS4wNiAzLjQgMS4wNiA1LjIzcy0uMzYgMy41Ny0xLjA2IDUuMjNjLS42NyAxLjYtMS42NCAzLjA0LTIuODggNC4yN3onLz48L3N2Zz4=");
}
</style>
</head>
<body>
<p id="dummy" style="height:300px;">DUMMY for space</p>
<ul id="sns">
<li>
<a class="pocket" href="#">pocket</a>
</li>
<li>
<a class="facebook" href="#">facebook</a>
</li>
<li>
<a class="twitter" href="#">twitter</a>
</li>
<li>
<a class="github" href="#">github</a>
</li>
<li>
<a class="googlep" href="#">google+</a>
</li>
<li>
<a class="line" href="#">line</a>
</li>
<li>
<a class="hateb" href="#">hateb</a>
</li>
<li>
<a class="feedly" href="#">feedly</a>
</li>
</ul>
<script>
// マウスホバーした時の拡大率
var zoom = 4;
(function(zoom) {
'use strict'
//
if('ontouchstart' in window) return;
var anime;
var mouseX;
var dock = document.getElementById('sns');
dock.classList.add('dock');
var icons = dock.children;
var defaultWidth = icons[0].offsetWidth;
var bound = defaultWidth * 3.14;
dock.style.width = defaultWidth * (icons.length -1) +'px';
dock.style.height = defaultWidth +'px';
[].forEach.call(icons, function(icon, i) {
var span = document.createElement('span');
var anchor = icon.getElementsByTagName('a')[0];
var text = anchor.textContent;
span.textContent = text;
icon.appendChild(span);
anchor.textContent = '';
// icon.style.transition = ...でもOK
icon.setAttribute('style', 'transition:.3s;left:' + i * defaultWidth + 'px');
});
// 50/sのフレームレートで実行される、各アイコンの位置や大きさを決めている関数
function scaling(x) {
for(var i = icons.length; i--;) {
var icon = icons[i];
var distance = (i * defaultWidth + defaultWidth / 2) - x;
if(-bound < distance && distance < bound) {
var rad = distance / defaultWidth * .5;
var currentScale = icon.getBoundingClientRect().width / icon.offsetWidth;
var scale = currentScale + (1 + (zoom - 1) * Math.cos(rad) - currentScale)/5;
var currentLeft = icon.offsetLeft;
var left = currentLeft + (i * defaultWidth + 2 * (zoom - 1) * defaultWidth * Math.sin(rad) -currentLeft)/5;
// icon.style.left .., icon.style.transform = ..じゃないのは、速度を
// 優先したため
icon.setAttribute('style', 'left:' + left + 'px;transform:scale(' + scale + ')');
} else {
var left;
if(-bound < distance) {
left = i * defaultWidth + 2.05 * (zoom - 1) * defaultWidth;
} else {
left = i * defaultWidth - 2.05 * (zoom - 1) * defaultWidth;
}
var currentLeft = icon.offsetLeft;
var currentScale = icon.getBoundingClientRect().width / icon.offsetWidth;
icon.setAttribute('style', 'left:' + (currentLeft + (left - currentLeft)/5) + 'px;transform:scale('+(currentScale + (1-currentScale)/5)+')');
}
}
}
dock.addEventListener('mousemove', function(e) {
mouseX = e.clientX - dock.getBoundingClientRect().left;
});
// dockの上にマウスが入ったときに各アイコンの拡大処理をスタート
dock.addEventListener('mouseenter', function(e) {
// フレームレート 50/s で各アイコンを拡大/縮小する
anime = setInterval(function() {
scaling(mouseX);
}, 20);
// dock自体も拡大しないと、iconの隙間にマウスが入ったときに
// 不意に縮小する
dock.style.height = defaultWidth * zoom + 'px';
dock.style.marginTop = -(zoom - 1) * defaultWidth + 'px';
});
// マウスがdockから離れたとき
dock.addEventListener('mouseleave', function(e) {
clearInterval(anime);
dock.style.height = defaultWidth + 'px';
dock.style.marginTop = 0;
// 通常の状態に戻る縮小処理はJavaScriptじゃなくてcssのtransitionでやってる
// jsでやると、全ての処理が終了するまでanime処理を終わらせられなくて、その間に
// またmouseenterになったときの処理が面倒
for(var i = icons.length; i--;) {
// tansform:scaleは書かなくても、setAttributeでやっていると
// transform属性自体削除されているのでscale(1)扱いになる
icons[i].setAttribute('style', 'transition: .3s;left:' + i * defaultWidth + 'px');
}
});
}(zoom));
</script>
</body>
</html>
このコードをコピーしてHTMLファイルを作れば、簡単に同じものが再現できますので、ぜひやってみてください。