2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【コピペだけ】NuxtJSでCodePenのクローンを作成しよう

Posted at

皆さん、CodePenは使ったことがありますか?CodePenはHTML、CSS、JavaScriptを入力してその結果をブラウザで見れるウェブアプリになります。CodePenはCSSやJavaScriptを使ってアニメーションを探したりするときにつかったりします。

今日はシンプルなCodePenのクローンを作成したので紹介します。

CodePenの例:

image.png

今日は自作でJavaScript、HTML、CSSを入力できるエディターとその結果が表示されるアプリのベースを作っていきます。

完成形は画像のようになります。完成したコードはこちらのGitHubリポからどうぞ。

image.png

この記事を読む前に知っておくこと

このコードで使用するテクニック

  • iFrameのsrcdocNuxtのHTML内にユーザー用のHTMLドキュメントを作成する
  • Monaco EditorでVSCodeのエディターを作成
  • ComputedでJavaScript、HTML、CSSに変更があった都度に結果をレンダー
  • PrimeVueのUIコンポーネントをSSRで使用

リポジトリのクローン

では下記のコマンドを使用してリポジトリからデモアプリをセットアップします。

git clone git@github.com:TraitOtaku/js-builder.git
cd js-builder
npm i
//VSCode
code .
npm run dev

ファイル構成

nuxt.config.ts

Nuxtのコンフィグファイルです。

monaco editorのモジュールの登録と、PrimeVueのUIライブラリで使うスタイルのインポートをしています。

export default defineNuxtConfig({
 modules: [
   'nuxt-monaco-editor'
 ],
 css:[
   'primevue/resources/themes/saga-blue/theme.css', //テーマカラーは色々あります。
   'primevue/resources/primevue.min.css',
   'primeicons/primeicons.css',
   '~/assets/css/reset.css', //Custom Prime Flex
   '~/assets/css/primeflex.css', //Custom Prime Flex
 ],
 build: {
   transpile: ['primevue']
 }
})

plugins/primevue.js

pluginsディレクトリはNuxtで指定されているディレクトリでレンダー時にNuxtが自動で読み込んでくれます。ここでは、グローバルで使えるPrimeVueのコンポーネントを登録しています。

詳しいPrimeVueのセットアップの仕方の記事はこちらから。

pages/code.vue

pagesも同じようにNuxtで指定されている、自動で読み込んでくれるディレクトリです。pages/index.vueがランディングページになります。

/codeのページに行くとcode.vueのページ(コンポーネント)が読み込まれます。

components/code/editor.vue

componentsディレクトもNuxtで指定されているディレクトリになります。

ここで登録されたvueファイルはグロバーバルで<CodeEditor>のように使用することができます。ファイル名の最初が大文字になることと、componetsディレクトリからディレクトリ名を当ててパスをしてするような方法になります。

TabViewとTabPanel:PrimeVueのコンポーネントです。

MonacoEditor:monaco editorのNuxtライブラリで使えるコンポーネントです。

valueJS、valueHTML、valueCSS:初期値とユーザーが入力できるリアクティブな値

iFrameのsrcdoc:iframeDoc変数(computedのHTMLファイルをレンダーします)

monacoConfig:テキストエディターのパラメーター(ダークテーマ、ミニマップの表示、行数の表示など)

<template>
 <div class="flex-row">
   <div class="flex flex-wrap">
     <div class="col-12 md:col-6">
       <TabView>
         <TabPanel header="JavaScript">
           <MonacoEditor
             class="monaco-editor-container"
             lang="javascript"
             v-model="valueJS"
             :options="monacoConfig"
           />
         </TabPanel>
         <TabPanel header="HTML">
           <MonacoEditor
             class="monaco-editor-container"
             lang="html"
             v-model="valueHTML"
             :options="monacoConfig"
           />
         </TabPanel>
         <TabPanel header="CSS">
           <MonacoEditor
             class="monaco-editor-container"
             lang="css"
             v-model="valueCSS"
             :options="monacoConfig"
           />
         </TabPanel>
       </TabView>
     </div>
     <div class="col-12 md:col-6">
       <TabView>
         <TabPanel header="Result">
           <div class="iframe-container">
             <iframe
               :srcdoc="iframeDoc"
               frameborder="0"
               class="iframe-result h-full w-full"


             ></iframe>
           </div>
         </TabPanel>
         <TabPanel header="console"> </TabPanel>
       </TabView>
     </div>
   </div>
 </div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const valueJS = ref(`
var d = new Date();
var monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
var date = document.getElementById("date");
var time = document.getElementById("time");
function getDate() {
   date.innerHTML = monthNames[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
}
function timer() {
   setTimeout(timer, 1000);
   var d = new Date();
   var hours = d.getHours();
   var minutes = d.getMinutes();
   var ampm = hours <= 11 ? 'am' : 'pm';
   var strTime = [hours % 12,
                 (minutes < 10 ? "0" + minutes : minutes)
                 ].join(':') + ampm;
   time.innerHTML = strTime;
   setTimeout(timer, 1000);
}
getDate();
timer();
`);
const valueHTML = ref(`
   <div class="wrapper">
       <div class="content">
           <h1 id="date" class="date"></h1>
           <h3 id="time" class="time"></h3>
       </div>
   </div>


`);
const valueCSS = ref(
 `
 * {
   margin: 0;
   padding: 0;
}
body {
   background: #fff;
   font-family: lato,sans-serif;
   color: #bdc3c7;
}
.wrapper {
   width: 400px;
   margin: 10% auto;
}
.content {
   background: #fff;
   box-shadow: 0 0 10px rgba(0,0,0,0.5);
   width: 400px;
}
.date, .time {
   color: #bdc3c7;
   font-weight: 300;
   font-size: 1.5em;
   padding: 20px;
}
.date {
   border-bottom: 2px solid #eee;
}
.time {
   font-size: 3em;
}
`
);
const iframeDoc = computed(() => {
 return `
 <!DOCTYPE html>
   <html lang="en">
     <head>
       <style>
         ${valueCSS.value}
       </style>
     </head>
     <body>
       ${valueHTML.value}
       <script>${valueJS.value}<\/script>
     </body>
   </html>
`;
});
const monacoConfig = {
 theme: 'vs-dark',
 minimap: { enabled: false },
 fontSize: 16,
 scrollBeyondLastLine: false,
 automaticLayout: true,
 wordWrap: 'on',
 wordWrapColumn: 80,
 wordWrapMinified: true,
 lineNumbers: 'on',
 lineNumbersMinChars: 3,
 lineDecorationsWidth: 10,
 lineHeight: 20,
 // glyphMargin: true,
 folding: true,
 renderLineHighlight: 'all',
 renderIndentGuides: true,
 renderFinalNewline: true,
 renderLineHighlightOnlyWhenFocus: true,
 renderValidationDecorations: 'on',
 cursorBlinking: 'blink',
 cursorSmoothCaretAnimation: true,
 cursorStyle: 'line',
};
</script>
<style>
.monaco-editor-container {
 min-height: 530px;
 height: 530px;
}
.iframe-container {
 width: 100%;
 height: 530px;
}
#iframe-result {
 width: 100svw;
 min-height: 530px;
 height: 800px;
 border: none;
}
</style>

上記で説明した部分が今回のメインとなるロジックになります。

追加できなかった部分

JavaScriptを実行した際にconsole.logの値をブラウザ(consoleタブ)に表示

これは、以下の理由より完成できませんでした。

  • valueJS→iframe内のHTMLのみでレンダー→データがemitされないのでNuxtでデータをつかめない

自由にリポのクローンをしてください。もしconsole.log()の出し方が分かる方、是非教えてください。
お疲れ様でした。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?