vue-camera-kit
Version:
A versatile Vue 3 camera component for capturing photos and videos with advanced features
7 lines (6 loc) • 13.7 kB
JavaScript
(function(b,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(b=typeof globalThis<"u"?globalThis:b||self,e(b.VueCameraKit={},b.Vue))})(this,function(b,e){"use strict";var B=document.createElement("style");B.textContent=`.camera-overlay[data-v-49bfc96b]{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:2}.grid-overlay[data-v-49bfc96b]{position:absolute;top:0;left:0;right:0;bottom:0;border:2px solid rgba(255,255,255,.5)}.watermark[data-v-49bfc96b]{position:absolute;z-index:3;object-fit:contain;pointer-events:none;filter:drop-shadow(0 2px 4px rgba(0,0,0,.3))}.rule-of-thirds[data-v-49bfc96b]{background-image:linear-gradient(rgba(255,255,255,.5) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.5) 1px,transparent 1px);background-size:33.33% 33.33%;background-position:-1px -1px}.golden-ratio[data-v-49bfc96b]{background-image:linear-gradient(rgba(255,255,255,.5) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.5) 1px,transparent 1px);background-size:38.2% 38.2%;background-position:-1px -1px}.center[data-v-49bfc96b]{background-image:linear-gradient(rgba(255,255,255,.5) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.5) 1px,transparent 1px);background-size:50% 50%;background-position:-1px -1px}.vue-camera-kit[data-v-14158381]{position:relative;width:100%;max-width:var(--a1462450);margin:0 auto;overflow:hidden;background:#000;border-radius:8px}.vue-camera-kit video[data-v-14158381]{width:100%;height:auto;max-width:100%;object-fit:cover}.vue-camera-kit.is-recording video[data-v-14158381]{border:2px solid red}.camera-controls[data-v-14158381]{position:absolute;bottom:20px;left:0;right:0;display:flex;justify-content:center;gap:20px;padding:10px}.control-btn[data-v-14158381]{width:60px;height:60px;border-radius:50%;border:none;background:#fffc;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.control-btn[data-v-14158381]:hover{background:#ffffffe6;transform:scale(1.1)}.control-btn[data-v-14158381]:disabled{opacity:.5;cursor:not-allowed}.control-btn.record-video.is-recording[data-v-14158381]{background:#f00c}.preview-modal[data-v-14158381]{position:fixed;top:0;left:0;right:0;bottom:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000}.preview-content[data-v-14158381]{max-width:90%;max-height:90vh;background:#fff;padding:20px;border-radius:8px}.preview-content img[data-v-14158381],.preview-content video[data-v-14158381]{max-width:100%;max-height:70vh}.preview-controls[data-v-14158381]{margin-top:20px;display:flex;justify-content:center;gap:10px}.preview-controls button[data-v-14158381]{padding:10px 20px;border:none;border-radius:4px;background:#007bff;color:#fff;cursor:pointer;transition:background .3s ease}.preview-controls button[data-v-14158381]:hover{background:#0056b3}.control-btn.active[data-v-14158381]{background:#007bffcc;color:#fff}.control-btn.toggle-grid .icon[data-v-14158381],.control-btn.aspect-ratio .icon[data-v-14158381]{font-size:24px}@media (max-width: 768px){.camera-controls[data-v-14158381]{padding:15px;gap:15px}.control-btn[data-v-14158381]{width:50px;height:50px}}@supports (-webkit-touch-callout: none){.vue-camera-kit video[data-v-14158381]{position:relative;z-index:1;width:100%;height:100%;object-fit:cover}}
/*$vite$:1*/`,document.head.appendChild(B);function A(o){return e.getCurrentScope()?(e.onScopeDispose(o),!0):!1}function E(o){return typeof o=="function"?o():e.unref(o)}const S=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const D=Object.prototype.toString,T=o=>D.call(o)==="[object Object]",O=()=>{};function P(o){let r;function s(){return r||(r=o()),r}return s.reset=async()=>{const t=r;r=void 0,t&&await t},s}function G(o){var r;const s=E(o);return(r=s==null?void 0:s.$el)!=null?r:s}const U=S?window:void 0,V=S?window.navigator:void 0;function _(...o){let r,s,t,l;if(typeof o[0]=="string"||Array.isArray(o[0])?([s,t,l]=o,r=U):[r,s,t,l]=o,!r)return O;Array.isArray(s)||(s=[s]),Array.isArray(t)||(t=[t]);const n=[],d=()=>{n.forEach(c=>c()),n.length=0},p=(c,v,g,h)=>(c.addEventListener(v,g,h),()=>c.removeEventListener(v,g,h)),u=e.watch(()=>[G(r),E(l)],([c,v])=>{if(d(),!c)return;const g=T(v)?{...v}:v;n.push(...s.flatMap(h=>t.map(m=>p(c,h,m,g))))},{immediate:!0,flush:"post"}),f=()=>{u(),d()};return A(f),f}function I(){const o=e.ref(!1),r=e.getCurrentInstance();return r&&e.onMounted(()=>{o.value=!0},r),o}function z(o){const r=I();return e.computed(()=>(r.value,!!o()))}function W(o,r={}){const{controls:s=!1,navigator:t=V}=r,l=z(()=>t&&"permissions"in t);let n;const d={name:o},p=e.ref(),u=()=>{n&&(p.value=n.state)},f=P(async()=>{if(l.value){if(!n)try{n=await t.permissions.query(d),_(n,"change",u),u()}catch{p.value="prompt"}return n}});return f(),s?{state:p,isSupported:l,query:f}:p}function q(o={}){const{navigator:r=V,requestPermissions:s=!1,constraints:t={audio:!0,video:!0},onUpdated:l}=o,n=e.ref([]),d=e.computed(()=>n.value.filter(m=>m.kind==="videoinput")),p=e.computed(()=>n.value.filter(m=>m.kind==="audioinput")),u=e.computed(()=>n.value.filter(m=>m.kind==="audiooutput")),f=z(()=>r&&r.mediaDevices&&r.mediaDevices.enumerateDevices),c=e.ref(!1);let v;async function g(){f.value&&(n.value=await r.mediaDevices.enumerateDevices(),l==null||l(n.value),v&&(v.getTracks().forEach(m=>m.stop()),v=null))}async function h(){if(!f.value)return!1;if(c.value)return!0;const{state:m,query:y}=W("camera",{controls:!0});return await y(),m.value!=="granted"&&(v=await r.mediaDevices.getUserMedia(t),g()),c.value=!0,c.value}return f.value&&(s&&h(),_(r.mediaDevices,"devicechange",g),g()),{devices:n,ensurePermissions:h,permissionGranted:c,videoInputs:d,audioInputs:p,audioOutputs:u,isSupported:f}}const L={class:"camera-overlay"},$=["src","alt"],F=e.defineComponent({__name:"CameraOverlay",props:{showGrid:{type:Boolean,default:!1},gridType:{default:"rule-of-thirds"},aspectRatio:{default:"original"},watermark:{},watermarkAlt:{},watermarkPosition:{default:"bottom-right"},watermarkSize:{default:20}},setup(o){const r=o,s=e.computed(()=>({"top-left":{top:"10px",left:"10px"},"top-right":{top:"10px",right:"10px"},"bottom-left":{bottom:"10px",left:"10px"},"bottom-right":{bottom:"10px",right:"10px"},center:{top:"50%",left:"50%",transform:"translate(-50%, -50%)"}})[r.watermarkPosition||"bottom-right"]);return(t,l)=>(e.openBlock(),e.createElementBlock("div",L,[t.showGrid?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["grid-overlay",[t.gridType,t.aspectRatio]])},null,2)):e.createCommentVNode("",!0),t.watermark?(e.openBlock(),e.createElementBlock("img",{key:1,src:t.watermark,alt:t.watermarkAlt||"Watermark",class:"watermark",style:e.normalizeStyle([s.value,{width:`${t.watermarkSize}%`,maxWidth:"150px",opacity:.7}])},null,12,$)):e.createCommentVNode("",!0)]))}}),M=(o,r)=>{const s=o.__vccOpts||o;for(const[t,l]of r)s[t]=l;return s},x=M(F,[["__scopeId","data-v-49bfc96b"]]),Q=["width","height"],H={class:"camera-controls"},K=["disabled"],X=["disabled"],J={key:1,class:"preview-modal"},Y={class:"preview-content"},Z=["src"],ee=["src"],R=M(e.defineComponent({__name:"Camera",props:{showOverlay:{type:Boolean,default:!0},showGridButton:{type:Boolean,default:!0},showAspectRatioButton:{type:Boolean,default:!0},width:{default:640},height:{default:480},facingMode:{default:"environment"},photoQuality:{default:.92},videoConstraints:{default:()=>({})},showPreviewByDefault:{type:Boolean,default:!0},showGrid:{type:Boolean,default:!1},gridType:{default:"rule-of-thirds"},aspectRatio:{default:"original"},watermark:{},watermarkAlt:{},watermarkPosition:{default:"bottom-right"},watermarkSize:{default:20}},emits:["photo-captured","video-started","video-stopped","error"],setup(o,{expose:r,emit:s}){e.useCssVars(a=>({a1462450:a.width+"px"}));const t=o,l=s,n=e.ref(null),d=e.ref(null),p=e.ref(null),u=e.ref(!1),f=e.ref([]),c=e.ref(""),v=e.ref("photo"),g=e.ref(!1);q({requestPermissions:!0,onUpdated(){N()}});const h=e.ref([]),m=e.ref(t.showGrid),y=e.ref(t.aspectRatio),k=e.ref(t.facingMode),ae=e.computed(()=>!!(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia)),oe=e.defineComponent({props:{message:{type:String,required:!0}},template:`
<div class="camera-error">
<p>{{ message }}</p>
</div>
`}),N=async()=>{try{const a={video:{facingMode:k.value,width:{ideal:t.width},height:{ideal:t.height},...t.videoConstraints},audio:!0};d.value&&d.value.getTracks().forEach(w=>w.stop());const i=await navigator.mediaDevices.getUserMedia(a);d.value=i,n.value&&(n.value.srcObject=i,n.value.setAttribute("playsinline",""),n.value.setAttribute("webkit-playsinline",""),await n.value.play())}catch(a){console.error("Camera initialization error:",a),l("error",a)}},re=async()=>{try{if(h.value.length<=1)return;k.value=k.value==="user"?"environment":"user",d.value&&d.value.getTracks().forEach(i=>i.stop());const a={video:{facingMode:k.value,width:{ideal:t.width},height:{ideal:t.height},...t.videoConstraints},audio:!0};d.value=await navigator.mediaDevices.getUserMedia(a),n.value&&(n.value.srcObject=d.value,await n.value.play())}catch(a){console.error("Camera switch error:",a),l("error",a)}},ne=()=>{m.value=!m.value},C=["original","aspect-1-1","aspect-16-9","aspect-4-3","aspect-3-2"],ie=()=>{const i=(C.indexOf(y.value)+1)%C.length;y.value=C[i]},se=()=>{if(!n.value)return;const a=document.createElement("canvas");a.width=n.value.videoWidth,a.height=n.value.videoHeight;const i=a.getContext("2d");if(!i)return;i.drawImage(n.value,0,0);const w=a.toDataURL("image/jpeg",t.photoQuality);a.toBlob(j=>{j&&(c.value=w,v.value="photo",g.value=t.showPreviewByDefault,l("photo-captured",{dataUrl:w,blob:j}))},"image/jpeg",t.photoQuality)},le=()=>{u.value?pe():ce()},ce=async()=>{if(d.value)try{const a={mimeType:de(),videoBitsPerSecond:25e5};f.value=[],p.value=new MediaRecorder(d.value,a),p.value.ondataavailable=i=>{i.data.size>0&&f.value.push(i.data)},p.value.onstop=()=>{var w;const i=new Blob(f.value,{type:((w=p.value)==null?void 0:w.mimeType)||"video/webm"});l("video-stopped",{blob:i}),u.value=!1},p.value.start(),u.value=!0,l("video-started")}catch(a){console.error("Recording error:",a),l("error",a)}},de=()=>{const a=["video/webm;codecs=h264","video/webm","video/mp4","video/mp4;codecs=h264","video/webm;codecs=vp8,opus"];for(const i of a)if(MediaRecorder.isTypeSupported(i))return i;return"video/webm"},pe=()=>{p.value&&u.value&&(p.value.stop(),u.value=!1)},ue=()=>{g.value=!1},me=()=>{g.value=!1,c.value="",v.value==="video"&&(f.value=[])};e.onMounted(()=>{N()}),e.onBeforeUnmount(()=>{p.value&&u.value&&p.value.stop(),d.value&&d.value.getTracks().forEach(a=>a.stop())});const fe=()=>{},ve=e.computed(()=>({transform:k.value==="user"?"scaleX(-1)":"none",width:"100%",height:"auto",maxWidth:"100%",objectFit:"cover"}));return r({}),(a,i)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["vue-camera-kit",{"is-recording":u.value}])},[ae.value?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("video",{ref_key:"videoRef",ref:n,width:a.width,height:a.height,autoplay:"",playsinline:"","webkit-playsinline":"",muted:"",style:e.normalizeStyle(ve.value),onLoadedmetadata:fe},null,44,Q),a.showOverlay?(e.openBlock(),e.createBlock(x,{key:0,"show-grid":m.value,"grid-type":a.gridType,"aspect-ratio":a.aspectRatio,watermark:a.watermark,"watermark-alt":a.watermarkAlt,"watermark-position":a.watermarkPosition,"watermark-size":a.watermarkSize},null,8,["show-grid","grid-type","aspect-ratio","watermark","watermark-alt","watermark-position","watermark-size"])):e.createCommentVNode("",!0),e.createElementVNode("div",H,[h.value.length>1?(e.openBlock(),e.createElementBlock("button",{key:0,class:"control-btn switch-camera",onClick:re,disabled:u.value},i[0]||(i[0]=[e.createElementVNode("span",{class:"icon"},"🔄",-1)]),8,K)):e.createCommentVNode("",!0),a.showGridButton?(e.openBlock(),e.createElementBlock("button",{key:1,class:e.normalizeClass(["control-btn toggle-grid",{active:m.value}]),onClick:ne},i[1]||(i[1]=[e.createElementVNode("span",{class:"icon"},"⊞",-1)]),2)):e.createCommentVNode("",!0),a.showAspectRatioButton?(e.openBlock(),e.createElementBlock("button",{key:2,class:"control-btn aspect-ratio",onClick:ie},i[2]||(i[2]=[e.createElementVNode("span",{class:"icon"},"⊡",-1)]))):e.createCommentVNode("",!0),e.createElementVNode("button",{class:"control-btn capture-photo",onClick:se,disabled:u.value},i[3]||(i[3]=[e.createElementVNode("span",{class:"icon"},"📸",-1)]),8,X),e.createElementVNode("button",{class:e.normalizeClass(["control-btn record-video",{"is-recording":u.value}]),onClick:le},i[4]||(i[4]=[e.createElementVNode("span",{class:"icon"},"🎥",-1)]),2)]),g.value?(e.openBlock(),e.createElementBlock("div",J,[e.createElementVNode("div",Y,[v.value==="photo"?(e.openBlock(),e.createElementBlock("img",{key:0,src:c.value,alt:"Captured photo"},null,8,Z)):(e.openBlock(),e.createElementBlock("video",{key:1,src:c.value,controls:""},null,8,ee)),e.createElementVNode("div",{class:"preview-controls"},[e.createElementVNode("button",{onClick:ue},"Accept"),e.createElementVNode("button",{onClick:me},"Retake")])])])):e.createCommentVNode("",!0)],64)):(e.openBlock(),e.createBlock(e.unref(oe),{key:1,message:"Camera access is not supported in this browser"}))],2))}}),[["__scopeId","data-v-14158381"]]),te={install:o=>{o.component("Camera",R),o.component("CameraOverlay",x)}};b.Camera=R,b.CameraOverlay=x,b.default=te,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});